diff --git a/.env b/.env index 49dd99d04..8099dcd37 100644 --- a/.env +++ b/.env @@ -8,7 +8,7 @@ APP_SECRET=29ac4a5187930cd4b689aa0f3ee7cbc0 #--------------------------------# # MySql MOOC_DATABASE_DRIVER=pdo_mysql -MOOC_DATABASE_HOST=codelytv-php_ddd_skeleton-mooc-mysql +MOOC_DATABASE_HOST=codely-php_ddd_skeleton-mooc-mysql MOOC_DATABASE_PORT=3306 MOOC_DATABASE_NAME=mooc MOOC_DATABASE_USER=root @@ -18,22 +18,22 @@ MOOC_DATABASE_PASSWORD= #--------------------------------# # MySql BACKOFFICE_DATABASE_DRIVER=pdo_mysql -BACKOFFICE_DATABASE_HOST=codelytv-php_ddd_skeleton-mooc-mysql +BACKOFFICE_DATABASE_HOST=codely-php_ddd_skeleton-mooc-mysql BACKOFFICE_DATABASE_PORT=3306 BACKOFFICE_DATABASE_NAME=mooc BACKOFFICE_DATABASE_USER=root BACKOFFICE_DATABASE_PASSWORD= # Elasticsearch -BACKOFFICE_ELASTICSEARCH_HOST=127.0.0.1 +BACKOFFICE_ELASTICSEARCH_HOST=codely-php_ddd_skeleton-backoffice-elastic BACKOFFICE_ELASTICSEARCH_INDEX_PREFIX=backoffice # COMMON # #--------------------------------# # RabbitMQ -RABBITMQ_HOST=codelytv-php_ddd_skeleton-rabbitmq +RABBITMQ_HOST=codely-php_ddd_skeleton-rabbitmq RABBITMQ_PORT=5672 -RABBITMQ_LOGIN=codelytv +RABBITMQ_LOGIN=codely RABBITMQ_PASSWORD=c0d3ly RABBITMQ_EXCHANGE=domain_events RABBITMQ_MAX_RETRIES=5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7f12e1ff..39fe081e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - master + - main pull_request: jobs: @@ -12,20 +12,48 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - - name: Install dependencies + - run: docker compose pull + + - name: 🎻 Install dependencies run: make composer-install - - name: Start all the environment + - name: 🐳 Start all the environment run: make start - - name: Wait for the environment to get up + - name: πŸ”¦ Lint + run: make lint + + - name: 🏁 Static analysis + run: make static-analysis + + - name: πŸ—οΈ Architecture + run: make test-architecture + + - name: πŸ—‘οΈ Mess detector + run: make mess-detector + + - name: 🦭 Wait for the database to get up run: | while ! make ping-mysql &>/dev/null; do echo "Waiting for database connection..." sleep 2 done - - name: Run the tests + - name: πŸ§ͺ Wait for the Elasticsearch to get up + run: | + while ! make ping-elasticsearch &>/dev/null; do + echo "Waiting for elasticsearch connection..." + sleep 2 + done + + - name: 🐰 Wait for RabbitMQ to get up + run: | + while ! make ping-rabbitmq &>/dev/null; do + echo "Waiting for RabbitMQ connection..." + sleep 2 + done + + - name: βœ… Run the tests run: make test diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 5b7b1aedb..e99bf3b81 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -7,11 +7,12 @@ jobs: runs-on: ubuntu-latest name: Label the PR size steps: - - uses: codelytv/pr-size-labeler@v1.3.0 + - uses: codelytv/pr-size-labeler@v1.8.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} xs_max_size: '10' - s_max_size: '100' - m_max_size: '350' - l_max_size: '700' + s_max_size: '300' + m_max_size: '600' + l_max_size: '1400' fail_if_xl: 'true' + files_to_ignore: 'composer.lock' diff --git a/.gitignore b/.gitignore index 440d81077..a4b4f3437 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.env.*.local /apps/*/*/var/ +!/apps/*/*/var/.gitkeep /apps/*/*/build/ !/apps/*/*/build/supervisor/.gitkeep @@ -10,3 +11,5 @@ .phpunit.result.cache /build + +.php-cs-fixer.cache diff --git a/Dockerfile b/Dockerfile index 0f82fbe05..dd0f725bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,25 @@ -FROM php:7.4.2-fpm-alpine +FROM php:8.3-fpm-alpine WORKDIR /app RUN apk --update upgrade \ - && apk add --no-cache autoconf automake make gcc g++ icu-dev rabbitmq-c rabbitmq-c-dev \ - && pecl install amqp-1.9.4 \ - && pecl install apcu-5.1.18 \ - && pecl install xdebug-2.9.1 \ - && docker-php-ext-install -j$(nproc) \ + && apk add --no-cache autoconf automake make gcc g++ git bash icu-dev libzip-dev rabbitmq-c rabbitmq-c-dev linux-headers + +RUN pecl install apcu-5.1.23 && pecl install amqp-2.1.1 && pecl install xdebug-3.3.0 + +RUN docker-php-ext-install -j$(nproc) \ bcmath \ opcache \ intl \ - pdo_mysql \ - && docker-php-ext-enable \ - amqp \ - apcu \ - opcache + zip \ + pdo_mysql + +RUN docker-php-ext-enable amqp apcu opcache + +RUN curl -sS https://get.symfony.com/cli/installer | bash -s - --install-dir /usr/local/bin COPY etc/infrastructure/php/ /usr/local/etc/php/ + +# allow non-root users have home +RUN mkdir -p /opt/home +RUN chmod 777 /opt/home +ENV HOME /opt/home diff --git a/Makefile b/Makefile index e067bc52e..efec06c20 100644 --- a/Makefile +++ b/Makefile @@ -1,61 +1,54 @@ -.PHONY: build deps composer-install composer-update composer reload test run-tests start stop destroy doco rebuild start-local ping-mysql - current-dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -build: deps start +composer-install: + @docker run --rm $(INTERACTIVE) --volume $(current-dir):/app --user $(id -u):$(id -g) \ + composer:2.6.4 install \ + --ignore-platform-reqs \ + --no-ansi -deps: composer-install +test: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/phpunit --testsuite mooc + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/phpunit --testsuite shared + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/behat -p mooc_backend --format=progress -v + docker exec codely-php_ddd_skeleton-backoffice_backend-php ./vendor/bin/phpunit --testsuite backoffice -# 🐘 Composer -composer-install: CMD=install -composer-update: CMD=update -composer composer-install composer-update: - @docker run --rm --interactive --volume $(current-dir):/app --user $(id -u):$(id -g) \ - clevyr/prestissimo $(CMD) \ - --ignore-platform-reqs \ - --no-ansi \ - --no-interaction +static-analysis: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/psalm --output-format=github --shepherd -reload: - @docker-compose exec php-fpm kill -USR2 1 - @docker-compose exec nginx nginx -s reload +lint: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/ecs check -test: - @docker exec codelytv-php_ddd_skeleton-php make run-tests +test-architecture: + docker exec codely-php_ddd_skeleton-mooc_backend-php php -d memory_limit=4G ./vendor/bin/phpstan analyse --error-format=github + +mess-detector: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/phpmd apps,src,tests github phpmd.xml -run-tests: - mkdir -p build/test_results/phpunit - ./vendor/bin/phpunit --exclude-group='disabled' --log-junit build/test_results/phpunit/junit.xml tests - ./vendor/bin/behat -p mooc_backend --format=progress -v +start: + @if [ ! -f .env.local ]; then echo '' > .env.local; fi + UID=${shell id -u} GID=${shell id -g} docker compose up --build -d + make clean-cache -# 🐳 Docker Compose -start: CMD=up -d -stop: CMD=stop -destroy: CMD=down +stop: + UID=${shell id -u} GID=${shell id -g} docker compose stop -# Usage: `make doco CMD="ps --services"` -# Usage: `make doco CMD="build --parallel --pull --force-rm --no-cache"` -doco start stop destroy: - @docker-compose $(CMD) +destroy: + UID=${shell id -u} GID=${shell id -g} docker compose down rebuild: - docker-compose build --pull --force-rm --no-cache - make deps + docker compose build --pull --force-rm --no-cache + make install make start -prepare-local: - curl -sS https://get.symfony.com/cli/installer | bash +ping-mysql: + @docker exec codely-php_ddd_skeleton-mooc-mysql mysqladmin --user=root --password= --host "127.0.0.1" ping --silent -start-local: - symfony serve --dir=apps/mooc/backend/public --port=8030 -d --no-tls --force-php-discovery - symfony serve --dir=apps/backoffice/frontend/public --port=8032 -d --no-tls --force-php-discovery - symfony serve --dir=apps/backoffice/backend/public --port=8034 -d --no-tls --force-php-discovery +ping-elasticsearch: + @curl -I -XHEAD localhost:9200 -stop-local: - symfony server:stop --dir=apps/mooc/backend/public - symfony server:stop --dir=apps/backoffice/frontend/public - symfony server:stop --dir=apps/backoffice/backend/public - -ping-mysql: - @docker exec codelytv-php_ddd_skeleton-mooc-mysql mysqladmin --user=root --password= --host "127.0.0.1" ping --silent +ping-rabbitmq: + @docker exec codely-php_ddd_skeleton-rabbitmq rabbitmqctl ping --silent +clean-cache: + @rm -rf apps/*/*/var + @docker exec codely-php_ddd_skeleton-mooc_backend-php ./apps/mooc/backend/bin/console cache:warmup diff --git a/README.md b/README.md index 7abbf2698..85d72efbe 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,79 @@

- - + + + + + Codely logo +

- 🐘🎯 Hexagonal Architecture, DDD & CQRS in PHP Symfony + 🐘🎯 Hexagonal Architecture, DDD & CQRS in PHP

- codely.tv + Codely Open Source projects CodelyTV Courses - Symfony 5.0 - CI pipeline status + Symfony 7 + Type Coverage + CI pipeline status

- Example of a PHP application following Domain-Driven Design (DDD) and - Command Query Responsibility Segregation (CQRS) principles keeping the code as simple as possible. + Example of a PHP application using Domain-Driven Design (DDD) and Command Query Responsibility Segregation + (CQRS) principles keeping the code as simple as possible.

Take a look, play and have fun with this. - Stars are welcomed 😊 -
-
- Explore the docs » + Stars are welcome 😊

View Demo Β· - Report Bug + Report a bug Β· - Request Feature + Request a feature

- - -## Table of Contents - -* [πŸš€ Environment setup](#-environment-setup) - * [🐳 Needed tools](#-needed-tools) - * [πŸ› οΈ Environment configuration](#-environment-configuration) - * [🌍 Application execution](#-application-execution) - * [βœ… Tests execution](#-tests-execution) -* [πŸ€” Project explanation](#-project-explanation) - * [Bounded Contexts](#-bounded-contexts) - * [Hexagonal Architecture](#-hexagonal-architecture) - * [Aggregates](#aggregates) - * [Command Bus](#command-bus) - * [Query Bus](#query-bus) - * [Event Bus](#event-bus) -* [🀝 Contributing](#-contributing) -* [🀩 Extra](#-extra) - -## πŸš€ Environment setup +## πŸš€ Environment Setup ### 🐳 Needed tools 1. [Install Docker](https://www.docker.com/get-started) -2. Clone this project: `git clone https://github.com/CodelyTV/cqrs-ddd-php-example cqrs-ddd-php-example` -3. Move to the project folder: `cd cqrs-ddd-php-example` +2. Clone this project: `git clone https://github.com/CodelyTV/php-ddd-example php-ddd-example` +3. Move to the project folder: `cd php-ddd-example` ### πŸ› οΈ Environment configuration -1. Create a local environment file if needed: `cp .env .env.local` -3. Add `api.codelytv.localhost` domain to your local hosts: `echo "127.0.0.1 api.codelytv.localhost"| sudo tee -a /etc/hosts > /dev/null` +1. Create a local environment file (`cp .env .env.local`) if you want to modify any parameter -### 🌍 Application execution +### πŸ”₯ Application execution -1. Install PHP dependencies and bring up the project Docker containers with Docker Compose: `make build` -2. Go to [the API health check page](http://api.codelytv.localhost:8030/health-check) +1. Install all the dependencies and bring up the project with Docker executing: `make build` +2. Then you'll have 3 apps available (2 APIs and 1 Frontend): + 1. [Mooc Backend](apps/mooc/backend): http://localhost:8030/health-check + 2. [Backoffice Backend](apps/backoffice/backend): http://localhost:8040/health-check + 3. [Backoffice Frontend](apps/backoffice/frontend): http://localhost:8041/health-check ### βœ… Tests execution -1. Install PHP dependencies if you haven't done so: `make deps` -2. Execute Behat and PHP Unit tests: `make test` +1. Install the dependencies if you haven't done it previously: `make deps` +2. Execute PHPUnit and Behat tests: `make test` -## πŸ€” Project explanation +## πŸ‘©β€πŸ’» Project explanation -This project tries to be a MOOC (Massive Open Online Course) platform. -It has a [Web](apps/backoffice/frontend/src/Controller), an [API](apps/mooc/backend/src/Controller) and -some [Consumers](apps/mooc/backend/src/Command). +This project tries to be a MOOC (Massive Open Online Course) platform. It's decoupled from any framework, but it has +some Symfony and Laravel implementations. ### ⛱️ Bounded Contexts -* [Mooc](src/Mooc): Place to look in if you wanna see some code πŸ™‚. Massive Open Online Courses public platform with users, videos, notifications, and so on -* [Backoffice](src/Backoffice): Here you'll find the use cases needed by the Customer Support department in order to manage users, courses, videos, and so on. +- [Mooc](src/Mooc): Place to look in if you wanna see some code πŸ™‚. Massive Open Online Courses public platform with users, videos, notifications, and so on. +- [Backoffice](src/Backoffice): Here you'll find the use cases needed by the Customer Support department in order to manage users, courses, videos, and so on. ### 🎯 Hexagonal Architecture -This repository follow the Hexagonal Architecture pattern. Also is structured using `modules`. +This repository follows the Hexagonal Architecture pattern. Also, it's structured using `modules`. With this, we can see that the current structure of a Bounded Context is: ```scala @@ -127,38 +111,56 @@ src ``` #### Repository pattern + Our repositories try to be as simple as possible usually only containing 2 methods `search` and `save`. -If we need some query with more filters we use the `Strategy` pattern also known as `Criteria` pattern. So we add a +If we need some query with more filters we use the `Specification` pattern also known as `Criteria` pattern. So we add a `searchByCriteria` method. You can see an example [here](src/Mooc/Courses/Domain/CourseRepository.php) and its implementation [here](src/Mooc/Courses/Infrastructure/Persistence/DoctrineCourseRepository.php). ### Aggregates + You can see an example of an aggregate [here](src/Mooc/Courses/Domain/Course.php). All aggregates should -extends the [AggregateRoot](src/Shared/Domain/Aggregate/AggregateRoot.php). +extend the [AggregateRoot](src/Shared/Domain/Aggregate/AggregateRoot.php). ### Command Bus + There is 1 implementations of the [command bus](src/Shared/Domain/Bus/Command/CommandBus.php). -1. [Sync](src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php) using the Symfony Message Bus +1. [Sync](src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php) using the Symfony Message Bus. + ### Query Bus + The [Query Bus](src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php) uses the Symfony Message Bus. ### Event Bus + The [Event Bus](src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php) uses the Symfony Message Bus. The [MySql Bus](src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php) uses a MySql+Pulling as a bus. The [RabbitMQ Bus](src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php) uses RabbitMQ C extension. +## πŸ“± Monitoring + +Every time a domain event is published it's exported to Prometheus. You can access to the Prometheus panel [here](http://localhost:9999/). + ## πŸ€” Contributing + There are some things missing (add swagger, improve documentation...), feel free to add this if you want! If you want some guidelines feel free to contact us :) ## 🀩 Extra -This code was show in the [From framework coupled code to #microservices through #DDD](http://codely.tv/screencasts/codigo-acoplado-framework-microservicios-ddd) talk and doubts where answered in [DDD y CQRS: Preguntas Frecuentes](http://codely.tv/screencasts/ddd-cqrs-preguntas-frecuentes/) video. + +This code was shown in the [From framework coupled code to #microservices through #DDD](http://codely.tv/blog/screencasts/codigo-acoplado-framework-microservicios-ddd) talk and doubts where answered in the [DDD y CQRS: Preguntas Frecuentes](https://codely.com/blog/ddd-cqrs-preguntas-frecuentes) video. + πŸŽ₯ Used in the CodelyTV Pro courses: -* [πŸ‡ͺπŸ‡Έ DDD in PHP](https://pro.codely.tv/library/ddd-en-php/about/) -* [πŸ‡ͺπŸ‡Έ Arquitectura Hexagonal](https://pro.codely.tv/library/arquitectura-hexagonal/66748/about/) -* [πŸ‡ͺπŸ‡Έ CQRS: Command Query Responsibility Segregation](https://pro.codely.tv/library/cqrs-command-query-responsibility-segregation-3719e4aa/62554/about/) -* [πŸ‡ͺπŸ‡Έ ComunicaciΓ³n entre microservicios: Event-Driven Architecture](https://pro.codely.tv/library/comunicacion-entre-microservicios-event-driven-architecture/74823/about/) + +- [πŸ‡ͺπŸ‡Έ DDD in PHP](https://pro.codely.tv/library/ddd-en-php/about/) +- [πŸ‡ͺπŸ‡Έ Arquitectura Hexagonal](https://pro.codely.tv/library/arquitectura-hexagonal/66748/about/) +- [πŸ‡ͺπŸ‡Έ CQRS: Command Query Responsibility Segregation](https://pro.codely.tv/library/cqrs-command-query-responsibility-segregation-3719e4aa/62554/about/) +- [πŸ‡ͺπŸ‡Έ ComunicaciΓ³n entre microservicios: Event-Driven Architecture](https://pro.codely.tv/library/comunicacion-entre-microservicios-event-driven-architecture/74823/about/) + +## 🌐 remember to visit our courses + +- [Courses codely](https://codely.com/cursos) diff --git a/apps/backoffice/backend/bin/console b/apps/backoffice/backend/bin/console index 91327c2e6..29bc8b83f 100755 --- a/apps/backoffice/backend/bin/console +++ b/apps/backoffice/backend/bin/console @@ -4,7 +4,7 @@ use CodelyTv\Apps\Backoffice\Backend\BackofficeBackendKernel; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Debug\Debug; +use Symfony\Component\ErrorHandler\Debug; if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { echo 'Warning: The console should be invoked via the CLI version of PHP, not the ' . PHP_SAPI . ' SAPI' . PHP_EOL; @@ -12,7 +12,7 @@ if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { set_time_limit(0); -require dirname(__DIR__) . '../../../../vendor/autoload.php'; +require dirname(__DIR__) . '/../../../vendor/autoload.php'; if (!class_exists(Application::class)) { throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.'); diff --git a/apps/backoffice/backend/config/bundles.php b/apps/backoffice/backend/config/bundles.php index 7de452252..15c297c9f 100644 --- a/apps/backoffice/backend/config/bundles.php +++ b/apps/backoffice/backend/config/bundles.php @@ -1,7 +1,9 @@ ['all' => true], - FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], - // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] + Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], + // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] ]; diff --git a/apps/backoffice/backend/config/routes/courses.yaml b/apps/backoffice/backend/config/routes/courses.yaml index 3778b0e0d..06ae46ee8 100644 --- a/apps/backoffice/backend/config/routes/courses.yaml +++ b/apps/backoffice/backend/config/routes/courses.yaml @@ -1,5 +1,5 @@ courses_get: path: /courses controller: CodelyTv\Apps\Backoffice\Backend\Controller\Courses\CoursesGetController - defaults: { auth: true } + defaults: { auth: false } methods: [GET] diff --git a/apps/backoffice/backend/config/routes/metrics.yaml b/apps/backoffice/backend/config/routes/metrics.yaml new file mode 100644 index 000000000..af36aa15d --- /dev/null +++ b/apps/backoffice/backend/config/routes/metrics.yaml @@ -0,0 +1,4 @@ +metrics_get: + path: /metrics + controller: CodelyTv\Apps\Backoffice\Backend\Controller\Metrics\MetricsController + methods: [GET] diff --git a/apps/backoffice/backend/config/services.yaml b/apps/backoffice/backend/config/services.yaml index 674162405..d0e25db62 100644 --- a/apps/backoffice/backend/config/services.yaml +++ b/apps/backoffice/backend/config/services.yaml @@ -84,10 +84,15 @@ services: arguments: - '%env(BACKOFFICE_ELASTICSEARCH_HOST)%' - '%env(BACKOFFICE_ELASTICSEARCH_INDEX_PREFIX)%' - - '%kernel.project_dir%/../../../databases/backoffice' + - '%kernel.project_dir%/../../../etc/databases/backoffice' - '%env(APP_ENV)%' public: true + CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus: + arguments: ['@CodelyTv\Shared\Infrastructure\Monitoring\PrometheusMonitor', 'backoffice_backend', '@CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqEventBus'] + + # -- IMPLEMENTATIONS SELECTOR -- + # -- IMPLEMENTATIONS SELECTOR -- - CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqEventBus' + CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus' CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository: '@CodelyTv\Backoffice\Courses\Infrastructure\Persistence\ElasticsearchBackofficeCourseRepository' diff --git a/apps/backoffice/backend/config/services_test.yaml b/apps/backoffice/backend/config/services_test.yaml index 934fc56a0..3a81827df 100644 --- a/apps/backoffice/backend/config/services_test.yaml +++ b/apps/backoffice/backend/config/services_test.yaml @@ -7,7 +7,7 @@ services: autowire: true CodelyTv\Tests\: - resource: '../../../../tests/src' + resource: '../../../../tests' # -- IMPLEMENTATIONS SELECTOR -- CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus' diff --git a/apps/backoffice/backend/public/index.php b/apps/backoffice/backend/public/index.php index b2f76b290..45fd7eab5 100644 --- a/apps/backoffice/backend/public/index.php +++ b/apps/backoffice/backend/public/index.php @@ -1,30 +1,32 @@ handle($request); $response->send(); $kernel->terminate($request, $response); diff --git a/apps/backoffice/backend/src/BackofficeBackendKernel.php b/apps/backoffice/backend/src/BackofficeBackendKernel.php index c66dd921c..321bf5524 100644 --- a/apps/backoffice/backend/src/BackofficeBackendKernel.php +++ b/apps/backoffice/backend/src/BackofficeBackendKernel.php @@ -1,6 +1,6 @@ getProjectDir() . '/config/bundles.php'; - foreach ($contents as $class => $envs) { - if ($envs[$this->environment] ?? $envs['all'] ?? false) { - yield new $class(); - } - } - } - - public function getProjectDir(): string - { - return dirname(__DIR__); - } - - protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void - { - $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', true); - $confDir = $this->getProjectDir() . '/config'; - - $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); - $loader->load($confDir . '/services_' . $this->environment . self::CONFIG_EXTS, 'glob'); - $loader->load($confDir . '/services/*' . self::CONFIG_EXTS, 'glob'); - } - - protected function configureRoutes(RouteCollectionBuilder $routes): void - { - $confDir = $this->getProjectDir() . '/config'; - - $routes->import($confDir . '/{routes}/*' . self::CONFIG_EXTS, '/', 'glob'); - } + use MicroKernelTrait; + + private const string CONFIG_EXTS = '.{xml,yaml}'; + + public function registerBundles(): iterable + { + $contents = require $this->getProjectDir() . '/config/bundles.php'; + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } + + public function getProjectDir(): string + { + return dirname(__DIR__); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); + $container->setParameter('.container.dumper.inline_class_loader', true); + $confDir = $this->getProjectDir() . '/config'; + + $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/services_' . $this->environment . self::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/services/*' . self::CONFIG_EXTS, 'glob'); + } } diff --git a/apps/backoffice/backend/src/Controller/Courses/CoursesGetController.php b/apps/backoffice/backend/src/Controller/Courses/CoursesGetController.php index da6050f93..4fcf89a26 100644 --- a/apps/backoffice/backend/src/Controller/Courses/CoursesGetController.php +++ b/apps/backoffice/backend/src/Controller/Courses/CoursesGetController.php @@ -1,6 +1,6 @@ queryBus = $queryBus; - } - - public function __invoke(Request $request): JsonResponse - { - /** @var BackofficeCoursesResponse $response */ - $response = $this->queryBus->ask( - new SearchBackofficeCoursesByCriteriaQuery( - $request->query->get('filters', []), - $request->query->get('order_by'), - $request->query->get('order'), - $request->query->get('limit'), - $request->query->get('offset') - ) - ); - - return new JsonResponse( - map($this->toArray(), $response->courses()), - 200, - ['Access-Control-Allow-Origin' => '*'] - ); - } - - private function toArray(): callable - { - return static function (BackofficeCourseResponse $course) { - return [ - 'id' => $course->id(), - 'name' => $course->name(), - 'duration' => $course->duration(), - ]; - }; - } + public function __construct(private QueryBus $queryBus) {} + + public function __invoke(Request $request): JsonResponse + { + $orderBy = $request->query->get('order_by'); + $order = $request->query->get('order'); + $limit = $request->query->get('limit'); + $offset = $request->query->get('offset'); + + /** @var BackofficeCoursesResponse $response */ + $response = $this->queryBus->ask( + new SearchBackofficeCoursesByCriteriaQuery( + (array) $request->query->get('filters'), + $orderBy, + $order, + $limit === null ? null : (int) $limit, + $offset === null ? null : (int) $offset + ) + ); + + return new JsonResponse( + map( + fn (BackofficeCourseResponse $course): array => [ + 'id' => $course->id(), + 'name' => $course->name(), + 'duration' => $course->duration(), + ], + $response->courses() + ), + 200, + ['Access-Control-Allow-Origin' => '*'] + ); + } } diff --git a/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php b/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php index 7125402d8..fe2256009 100644 --- a/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php +++ b/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php @@ -1,6 +1,6 @@ 'ok', - ] - ); - } + public function __invoke(Request $request): JsonResponse + { + return new JsonResponse( + [ + 'backoffice-backend' => 'ok', + ] + ); + } } diff --git a/apps/backoffice/backend/src/Controller/Metrics/MetricsController.php b/apps/backoffice/backend/src/Controller/Metrics/MetricsController.php new file mode 100644 index 000000000..e0ae59059 --- /dev/null +++ b/apps/backoffice/backend/src/Controller/Metrics/MetricsController.php @@ -0,0 +1,23 @@ +render($this->monitor->registry()->getMetricFamilySamples()); + + return new Response($result, 200, ['Content-Type' => RenderTextFormat::MIME_TYPE]); + } +} diff --git a/src/Mooc/Videos/Application/.gitkeep b/apps/backoffice/backend/var/.gitkeep similarity index 100% rename from src/Mooc/Videos/Application/.gitkeep rename to apps/backoffice/backend/var/.gitkeep diff --git a/apps/backoffice/frontend/bin/console b/apps/backoffice/frontend/bin/console index 70eea8863..d680ab53c 100755 --- a/apps/backoffice/frontend/bin/console +++ b/apps/backoffice/frontend/bin/console @@ -4,7 +4,7 @@ use CodelyTv\Apps\Backoffice\Frontend\BackofficeFrontendKernel; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Debug\Debug; +use Symfony\Component\ErrorHandler\Debug; if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { echo 'Warning: The console should be invoked via the CLI version of PHP, not the ' . PHP_SAPI . ' SAPI' . PHP_EOL; @@ -12,7 +12,7 @@ if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { set_time_limit(0); -require dirname(__DIR__) . '../../../../vendor/autoload.php'; +require dirname(__DIR__) . '/../../../vendor/autoload.php'; if (!class_exists(Application::class)) { throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.'); diff --git a/apps/backoffice/frontend/config/bundles.php b/apps/backoffice/frontend/config/bundles.php index c44a868b7..ef8009558 100644 --- a/apps/backoffice/frontend/config/bundles.php +++ b/apps/backoffice/frontend/config/bundles.php @@ -1,8 +1,10 @@ ['all' => true], - FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], - Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], - // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] + Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] ]; diff --git a/apps/backoffice/frontend/config/routes/courses.yaml b/apps/backoffice/frontend/config/routes/courses.yaml index 46d5ab683..bb3b30cde 100644 --- a/apps/backoffice/frontend/config/routes/courses.yaml +++ b/apps/backoffice/frontend/config/routes/courses.yaml @@ -1,9 +1,9 @@ courses_get: path: /courses - controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesGetController + controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesGetWebController methods: [GET] courses_post: path: /courses - controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesPostController + controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesPostWebController methods: [POST] diff --git a/apps/backoffice/frontend/config/routes/home.yaml b/apps/backoffice/frontend/config/routes/home.yaml index cbf8e1f5b..687b22467 100644 --- a/apps/backoffice/frontend/config/routes/home.yaml +++ b/apps/backoffice/frontend/config/routes/home.yaml @@ -1,4 +1,4 @@ home_get: path: / - controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Home\HomeGetController + controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Home\HomeGetWebController methods: [GET] diff --git a/apps/backoffice/frontend/config/routes/metrics.yaml b/apps/backoffice/frontend/config/routes/metrics.yaml new file mode 100644 index 000000000..65ad0004d --- /dev/null +++ b/apps/backoffice/frontend/config/routes/metrics.yaml @@ -0,0 +1,4 @@ +metrics_get: + path: /metrics + controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Metrics\MetricsController + methods: [GET] diff --git a/apps/backoffice/frontend/config/services.yaml b/apps/backoffice/frontend/config/services.yaml index 7f0e8d50e..23de22805 100644 --- a/apps/backoffice/frontend/config/services.yaml +++ b/apps/backoffice/frontend/config/services.yaml @@ -2,6 +2,10 @@ imports: - { resource: ../../../../src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml } - { resource: ../../../../src/Mooc/Shared/Infrastructure/Symfony/DependencyInjection/mooc_services.yaml } +framework: + session: + handler_id: null + services: _defaults: autoconfigure: true @@ -84,12 +88,15 @@ services: arguments: - '%env(BACKOFFICE_ELASTICSEARCH_HOST)%' - '%env(BACKOFFICE_ELASTICSEARCH_INDEX_PREFIX)%' - - '%kernel.project_dir%/../../../databases/backoffice' + - '%kernel.project_dir%/../../../etc/databases/backoffice' - '%env(APP_ENV)%' public: true + CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus: + arguments: ['@CodelyTv\Shared\Infrastructure\Monitoring\PrometheusMonitor', 'backoffice_frontend', '@CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqEventBus'] + # -- IMPLEMENTATIONS SELECTOR -- - CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqEventBus' + CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus' CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository: '@CodelyTv\Backoffice\Courses\Infrastructure\Persistence\ElasticsearchBackofficeCourseRepository' twig: diff --git a/apps/backoffice/frontend/config/services_test.yaml b/apps/backoffice/frontend/config/services_test.yaml index 934fc56a0..3a81827df 100644 --- a/apps/backoffice/frontend/config/services_test.yaml +++ b/apps/backoffice/frontend/config/services_test.yaml @@ -7,7 +7,7 @@ services: autowire: true CodelyTv\Tests\: - resource: '../../../../tests/src' + resource: '../../../../tests' # -- IMPLEMENTATIONS SELECTOR -- CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus' diff --git a/apps/backoffice/frontend/public/index.php b/apps/backoffice/frontend/public/index.php index 5e6452e04..6dd7c188e 100644 --- a/apps/backoffice/frontend/public/index.php +++ b/apps/backoffice/frontend/public/index.php @@ -1,30 +1,32 @@ handle($request); $response->send(); $kernel->terminate($request, $response); diff --git a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php index d5ef39b68..ae109b24a 100644 --- a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php +++ b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php @@ -1,6 +1,6 @@ getProjectDir() . '/config/bundles.php'; - foreach ($contents as $class => $envs) { - if ($envs[$this->environment] ?? $envs['all'] ?? false) { - yield new $class(); - } - } - } - - public function getProjectDir(): string - { - return dirname(__DIR__); - } - - protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void - { - $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', true); - $confDir = $this->getProjectDir() . '/config'; - - $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); - $loader->load($confDir . '/services_' . $this->environment . self::CONFIG_EXTS, 'glob'); - $loader->load($confDir . '/services/*' . self::CONFIG_EXTS, 'glob'); - } - - protected function configureRoutes(RouteCollectionBuilder $routes): void - { - $confDir = $this->getProjectDir() . '/config'; - - $routes->import($confDir . '/{routes}/*' . self::CONFIG_EXTS, '/', 'glob'); - } + use MicroKernelTrait; + + private const string CONFIG_EXTS = '.{xml,yaml}'; + + public function registerBundles(): iterable + { + $contents = require $this->getProjectDir() . '/config/bundles.php'; + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } + + public function getProjectDir(): string + { + return dirname(__DIR__); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); + $container->setParameter('.container.dumper.inline_class_loader', true); + $confDir = $this->getProjectDir() . '/config'; + + $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/services_' . $this->environment . self::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/services/*' . self::CONFIG_EXTS, 'glob'); + } } diff --git a/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php b/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php index c4b245986..c27dd2396 100644 --- a/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php +++ b/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php @@ -1,6 +1,6 @@ mySqlRepository = $mySqlRepository; - $this->elasticRepository = $elasticRepository; - } - - public function execute(InputInterface $input, OutputInterface $output): void - { - $courses = $this->mySqlRepository->searchAll(); - - foreach ($courses as $course) { - $this->elasticRepository->save($course); - } - } + public function __construct( + private readonly MySqlBackofficeCourseRepository $mySqlRepository, + private readonly ElasticsearchBackofficeCourseRepository $elasticRepository + ) { + parent::__construct(); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $courses = $this->mySqlRepository->searchAll(); + + foreach ($courses as $course) { + $this->elasticRepository->save($course); + } + + return 0; + } } diff --git a/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php b/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php index cf4c26b02..a9604d1b2 100644 --- a/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php +++ b/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php @@ -1,36 +1,36 @@ ask(new FindCoursesCounterQuery()); - public function __invoke(Request $request): Response - { - /** @var CoursesCounterResponse $coursesCounterResponse */ - $coursesCounterResponse = $this->ask(new FindCoursesCounterQuery()); + return $this->render( + 'pages/courses/courses.html.twig', + [ + 'title' => 'Courses', + 'description' => 'Courses CodelyTV - Backoffice', + 'courses_counter' => $coursesCounterResponse->total(), + 'new_course_id' => SimpleUuid::random()->value(), + ] + ); + } - return $this->render( - 'pages/courses/courses.html.twig', - [ - 'title' => 'Courses', - 'description' => 'Courses CodelyTV - Backoffice', - 'courses_counter' => $coursesCounterResponse->total(), - 'new_course_id' => Uuid::random()->value(), - ] - ); - } + protected function exceptions(): array + { + return []; + } } diff --git a/apps/backoffice/frontend/src/Controller/Courses/CoursesPostWebController.php b/apps/backoffice/frontend/src/Controller/Courses/CoursesPostWebController.php index 42a9b466f..625d2a1d0 100644 --- a/apps/backoffice/frontend/src/Controller/Courses/CoursesPostWebController.php +++ b/apps/backoffice/frontend/src/Controller/Courses/CoursesPostWebController.php @@ -1,6 +1,6 @@ validateRequest($request); - - return $validationErrors->count() - ? $this->redirectWithErrors('courses_get', $validationErrors, $request) - : $this->createCourse($request); - } - - private function validateRequest(Request $request): ConstraintViolationListInterface - { - $constraint = new Assert\Collection( - [ - 'id' => new Assert\Uuid(), - 'name' => [new Assert\NotBlank(), new Assert\Length(['min' => 1, 'max' => 255])], - 'duration' => [new Assert\NotBlank(), new Assert\Length(['min' => 4, 'max' => 100])], - ] - ); - - $input = $request->request->all(); - - return Validation::createValidator()->validate($input, $constraint); - } - - private function createCourse(Request $request): RedirectResponse - { - $this->dispatch( - new CreateCourseCommand( - $request->request->get('id'), - $request->request->get('name'), - $request->request->get('duration') - ) - ); - - return $this->redirectWithMessage( - 'courses_get', - sprintf('Feliciades, el curso %s ha sido creado!', $request->request->get('name')) - ); - } + public function __invoke(Request $request): RedirectResponse + { + $validationErrors = $this->validateRequest($request); + + return $validationErrors->count() + ? $this->redirectWithErrors('courses_get', $validationErrors, $request) + : $this->createCourse($request); + } + + protected function exceptions(): array + { + return []; + } + + private function validateRequest(Request $request): ConstraintViolationListInterface + { + $constraint = new Assert\Collection( + [ + 'id' => new Assert\Uuid(), + 'name' => [new Assert\NotBlank(), new Assert\Length(['min' => 1, 'max' => 255])], + 'duration' => [new Assert\NotBlank(), new Assert\Length(['min' => 4, 'max' => 100])], + ] + ); + + $input = $request->request->all(); + + return Validation::createValidator()->validate($input, $constraint); + } + + private function createCourse(Request $request): RedirectResponse + { + $this->dispatch( + new CreateCourseCommand( + (string) $request->request->get('id'), + (string) $request->request->get('name'), + (string) $request->request->get('duration') + ) + ); + + return $this->redirectWithMessage( + 'courses_get', + sprintf('Feliciades, el curso %s ha sido creado!', $request->request->getAlpha('name')) + ); + } } diff --git a/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php b/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php index 91c1dd4e8..a38a47d39 100644 --- a/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php +++ b/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php @@ -1,6 +1,6 @@ 'ok', - ] - ); - } + public function __invoke(Request $request): JsonResponse + { + return new JsonResponse( + [ + 'backoffice-frontend' => 'ok', + ] + ); + } } diff --git a/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php b/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php index b61b9781c..30e011847 100644 --- a/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php +++ b/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php @@ -1,6 +1,6 @@ render('pages/home.html.twig', [ + 'title' => 'Welcome', + 'description' => 'CodelyTV - Backoffice', + ]); + } - public function __invoke(Request $request): Response - { - return $this->render( - 'pages/home.html.twig', - [ - 'title' => 'Welcome', - 'description' => 'CodelyTV - Backoffice', - ] - ); - } + protected function exceptions(): array + { + return []; + } } diff --git a/apps/backoffice/frontend/src/Controller/Metrics/MetricsController.php b/apps/backoffice/frontend/src/Controller/Metrics/MetricsController.php new file mode 100644 index 000000000..a10a699ea --- /dev/null +++ b/apps/backoffice/frontend/src/Controller/Metrics/MetricsController.php @@ -0,0 +1,23 @@ +render($this->monitor->registry()->getMetricFamilySamples()); + + return new Response($result, 200, ['Content-Type' => RenderTextFormat::MIME_TYPE]); + } +} diff --git a/apps/backoffice/frontend/templates/pages/courses/partials/list_courses.html.twig b/apps/backoffice/frontend/templates/pages/courses/partials/list_courses.html.twig index 43b1d1afa..51b18eea1 100644 --- a/apps/backoffice/frontend/templates/pages/courses/partials/list_courses.html.twig +++ b/apps/backoffice/frontend/templates/pages/courses/partials/list_courses.html.twig @@ -142,12 +142,12 @@ const urlParts = inputs.map(input => input.name + "=" + input.value); - const url = "http://localhost:8034/courses?" + urlParts.join("&"); + const url = "http://localhost:8040/courses?" + urlParts.join("&"); addCoursesList(url); } diff --git a/src/Mooc/Videos/Domain/.gitkeep b/apps/backoffice/frontend/var/.gitkeep similarity index 100% rename from src/Mooc/Videos/Domain/.gitkeep rename to apps/backoffice/frontend/var/.gitkeep diff --git a/apps/bootstrap.php b/apps/bootstrap.php index f127db28d..8edbfc3b1 100644 --- a/apps/bootstrap.php +++ b/apps/bootstrap.php @@ -1,28 +1,17 @@ =1.2) -if (is_array($env = @include $rootPath . '/.env.local.php')) { - foreach ($env as $k => $v) { - $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); - } -} elseif (!class_exists(Dotenv::class)) { - throw new RuntimeException( - 'Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.' - ); -} else { - // load all the .env files - (new Dotenv(false))->loadEnv($rootPath . '/.env'); -} +(new Dotenv())->loadEnv($rootPath . '/.env'); -$_SERVER += $_ENV; -$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; -$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; +$_SERVER += $_ENV; +$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; +$_SERVER['APP_DEBUG'] ??= $_ENV['APP_DEBUG'] ?? $_SERVER['APP_ENV'] !== 'prod'; $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = - (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; + (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/apps/mooc/backend/bin/console b/apps/mooc/backend/bin/console index 7fc1ba297..d013ee7a8 100755 --- a/apps/mooc/backend/bin/console +++ b/apps/mooc/backend/bin/console @@ -4,7 +4,7 @@ use CodelyTv\Apps\Mooc\Backend\MoocBackendKernel; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Debug\Debug; +use Symfony\Component\ErrorHandler\Debug; if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { echo 'Warning: The console should be invoked via the CLI version of PHP, not the ' . PHP_SAPI . ' SAPI' . PHP_EOL; @@ -12,7 +12,7 @@ if (false === in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { set_time_limit(0); -require dirname(__DIR__) . '../../../../vendor/autoload.php'; +require dirname(__DIR__) . '/../../../vendor/autoload.php'; if (!class_exists(Application::class)) { throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.'); diff --git a/apps/mooc/backend/config/bundles.php b/apps/mooc/backend/config/bundles.php index 7de452252..15c297c9f 100644 --- a/apps/mooc/backend/config/bundles.php +++ b/apps/mooc/backend/config/bundles.php @@ -1,7 +1,9 @@ ['all' => true], - FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], - // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] + Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], + // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] ]; diff --git a/apps/mooc/backend/config/routes/metrics.yaml b/apps/mooc/backend/config/routes/metrics.yaml new file mode 100644 index 000000000..d680b5046 --- /dev/null +++ b/apps/mooc/backend/config/routes/metrics.yaml @@ -0,0 +1,4 @@ +metrics_get: + path: /metrics + controller: CodelyTv\Apps\Mooc\Backend\Controller\Metrics\MetricsController + methods: [GET] diff --git a/apps/mooc/backend/config/services.yaml b/apps/mooc/backend/config/services.yaml index a3a782c63..7d81f0953 100644 --- a/apps/mooc/backend/config/services.yaml +++ b/apps/mooc/backend/config/services.yaml @@ -91,6 +91,8 @@ services: - '%env(RABBITMQ_EXCHANGE)%' - !tagged codely.domain_event_subscriber + CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus: + arguments: ['@CodelyTv\Shared\Infrastructure\Monitoring\PrometheusMonitor', 'mooc_backend', '@CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqEventBus'] # -- IMPLEMENTATIONS SELECTOR -- - CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqEventBus' + CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus' diff --git a/apps/mooc/backend/config/services_test.yaml b/apps/mooc/backend/config/services_test.yaml index e3b6c91dd..8e4fe2146 100644 --- a/apps/mooc/backend/config/services_test.yaml +++ b/apps/mooc/backend/config/services_test.yaml @@ -7,7 +7,7 @@ services: autowire: true CodelyTv\Tests\: - resource: '../../../../tests/src' + resource: '../../../../tests' # Instance selector CodelyTv\Shared\Domain\RandomNumberGenerator: '@CodelyTv\Tests\Shared\Infrastructure\ConstantRandomNumberGenerator' diff --git a/apps/mooc/backend/public/index.php b/apps/mooc/backend/public/index.php index 00bf627a9..b8655fe09 100644 --- a/apps/mooc/backend/public/index.php +++ b/apps/mooc/backend/public/index.php @@ -1,30 +1,32 @@ handle($request); $response->send(); $kernel->terminate($request, $response); diff --git a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php index 9763b5625..16d96c2a6 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php @@ -1,6 +1,6 @@ consumer = $consumer; - $this->subscriberLocator = $subscriberLocator; - $this->connections = $connections; - - parent::__construct(); - } - - protected function configure(): void - { - $this - ->setDescription('Consume domain events from MySql') - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); - } - - protected function execute(InputInterface $input, OutputInterface $output): void - { - $quantityEventsToProcess = (int) $input->getArgument('quantity'); - - $consumer = pipe($this->consumer(), $this->clearConnections()); - - $this->consumer->consume($consumer, $quantityEventsToProcess); - } - - private function consumer(): callable - { - return function (DomainEvent $domainEvent) { - $subscribers = $this->subscriberLocator->allSubscribedTo(get_class($domainEvent)); - - foreach ($subscribers as $subscriber) { - $subscriber($domainEvent); - } - }; - } - - private function clearConnections(): callable - { - return function () { - $this->connections->clear(); - }; - } + public function __construct( + private readonly MySqlDoctrineDomainEventsConsumer $consumer, + private readonly DatabaseConnections $connections, + private readonly DomainEventSubscriberLocator $subscriberLocator + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $quantityEventsToProcess = (int) $input->getArgument('quantity'); + + $consumer = pipe($this->consumer(), fn () => $this->connections->clear()); + + $this->consumer->consume($consumer, $quantityEventsToProcess); + + return 0; + } + + private function consumer(): callable + { + return function (DomainEvent $domainEvent): void { + $subscribers = $this->subscriberLocator->allSubscribedTo($domainEvent::class); + + foreach ($subscribers as $subscriber) { + $subscriber($domainEvent); + } + }; + } } diff --git a/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php new file mode 100644 index 000000000..945784311 --- /dev/null +++ b/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php @@ -0,0 +1,91 @@ +transformers = [ + 'courses' => [ + DatabaseMutationAction::INSERT->value => DatabaseMutationToCourseCreatedDomainEvent::class, + DatabaseMutationAction::UPDATE->value => null, + DatabaseMutationAction::DELETE->value => null, + ], + ]; + } + + protected function configure(): void + { + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of mutations to process'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $totalMutations = (int) $input->getArgument('quantity'); + + $this->entityManager->wrapInTransaction(function (EntityManager $entityManager) use ($totalMutations) { + $mutations = $entityManager->getConnection() + ->executeQuery("SELECT * FROM mutations ORDER BY id ASC LIMIT $totalMutations FOR UPDATE") + ->fetchAllAssociative(); + + foreach ($mutations as $mutation) { + $transformer = $this->findTransformer($mutation['table_name'], $mutation['operation']); + + if ($transformer === null) { + echo sprintf("Ignoring %s %s\n", $mutation['table_name'], $mutation['operation']); + continue; + } + + $domainEvents = $transformer->transform($mutation); + + $this->eventBus->publish(...$domainEvents); + } + + $entityManager->getConnection()->executeStatement( + sprintf('DELETE FROM mutations WHERE id IN (%s)', implode(',', array_column($mutations, 'id'))) + ); + }); + + return 0; + } + + private function findTransformer(string $tableName, string $operation): ?DatabaseMutationToDomainEvent + { + if (!array_key_exists($tableName, $this->transformers) && array_key_exists( + $operation, + $this->transformers[$tableName] + )) { + throw new RuntimeException("Transformer not found for table $tableName and operation $operation"); + } + + /** @var class-string|null $class */ + $class = $this->transformers[$tableName][$operation]; + + return $class ? new $class() : null; + } +} diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php index 0799ec84c..72801af71 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php @@ -1,38 +1,34 @@ configurer = $configurer; - $this->exchangeName = $exchangeName; - $this->subscribers = $subscribers; - } - - protected function configure(): void - { - $this->setDescription('Configure the RabbitMQ to allow publish & consume domain events'); - } - - protected function execute(InputInterface $input, OutputInterface $output): void - { - $this->configurer->configure($this->exchangeName, ...iterator_to_array($this->subscribers)); - } + public function __construct( + private readonly RabbitMqConfigurer $configurer, + private readonly string $exchangeName, + private readonly Traversable $subscribers + ) { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->configurer->configure($this->exchangeName, ...iterator_to_array($this->subscribers)); + + return 0; + } } diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php index c43bc43da..c29c056d8 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php @@ -1,61 +1,59 @@ consumer = $consumer; - $this->connections = $connections; - $this->locator = $locator; - } - - protected function configure(): void - { - $this - ->setDescription('Consume domain events from the RabbitMQ') - ->addArgument('queue', InputArgument::REQUIRED, 'Queue name') - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); - } - - protected function execute(InputInterface $input, OutputInterface $output): void - { - $queueName = (string) $input->getArgument('queue'); - $eventsToProcess = (int) $input->getArgument('quantity'); - - repeat($this->consumer($queueName), $eventsToProcess); - } - - private function consumer(string $queueName): callable - { - return function () use ($queueName) { - $subscriber = $this->locator->withRabbitMqQueueNamed($queueName); - - $this->consumer->consume($subscriber, $queueName); - - $this->connections->clear(); - }; - } + public function __construct( + private readonly RabbitMqDomainEventsConsumer $consumer, + private readonly DatabaseConnections $connections, + private readonly DomainEventSubscriberLocator $locator + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('queue', InputArgument::REQUIRED, 'Queue name') + ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $queueName = $input->getArgument('queue'); + $eventsToProcess = (int) $input->getArgument('quantity'); + + repeat($this->consumer($queueName), $eventsToProcess); + + return 0; + } + + private function consumer(string $queueName): callable + { + return function () use ($queueName): void { + $subscriber = $this->locator->withRabbitMqQueueNamed($queueName); + + $this->consumer->consume($subscriber, $queueName); + + $this->connections->clear(); + }; + } } diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php index 2614595d6..646392bf3 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php @@ -1,92 +1,88 @@ locator = $locator; - } + protected function configure(): void + { + $this->addArgument('command-path', InputArgument::OPTIONAL, 'Path on this is gonna be deployed', '/var/www'); + } - protected function configure(): void - { - $this - ->setDescription('Generate the supervisor configuration for every RabbitMQ subscriber') - ->addArgument('command-path', InputArgument::OPTIONAL, 'Path on this is gonna be deployed', '/var/www'); - } + protected function execute(InputInterface $input, OutputInterface $output): int + { + $path = $input->getArgument('command-path'); - protected function execute(InputInterface $input, OutputInterface $output): void - { - $path = (string) $input->getArgument('command-path'); + each($this->configCreator($path), $this->locator->all()); - each($this->configCreator($path), $this->locator->all()); - } + return 0; + } - private function configCreator(string $path): callable - { - return function (DomainEventSubscriber $subscriber) use ($path) { - $queueName = RabbitMqQueueNameFormatter::format($subscriber); - $subscriberName = RabbitMqQueueNameFormatter::shortFormat($subscriber); + private function configCreator(string $path): callable + { + return function (DomainEventSubscriber $subscriber) use ($path): void { + $queueName = RabbitMqQueueNameFormatter::format($subscriber); + $subscriberName = RabbitMqQueueNameFormatter::shortFormat($subscriber); - $fileContent = str_replace( - [ - '{subscriber_name}', - '{queue_name}', - '{path}', - '{processes}', - '{events_to_process}', - ], - [ - $subscriberName, - $queueName, - $path, - self::NUMBERS_OF_PROCESSES_PER_SUBSCRIBER, - self::EVENTS_TO_PROCESS_AT_TIME, - ], - $this->template() - ); + $fileContent = str_replace( + ['{subscriber_name}', '{queue_name}', '{path}', '{processes}', '{events_to_process}', ], + [ + $subscriberName, + $queueName, + $path, + self::NUMBERS_OF_PROCESSES_PER_SUBSCRIBER, + self::EVENTS_TO_PROCESS_AT_TIME, + ], + $this->template() + ); - file_put_contents($this->fileName($subscriberName), $fileContent); - }; - } + file_put_contents($this->fileName($subscriberName), $fileContent); + }; + } - private function template(): string - { - return <<dispatch( + new CreateCourseCommand( + $id, + (string) $request->request->get('name'), + (string) $request->request->get('duration') + ) + ); - public function __invoke(string $id, Request $request): Response - { - $this->dispatch( - new CreateCourseCommand( - $id, - $request->request->get('name'), - $request->request->get('duration') - ) - ); + return new Response('', Response::HTTP_CREATED); + } - return new Response('', Response::HTTP_CREATED); - } + protected function exceptions(): array + { + return []; + } } diff --git a/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php b/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php index a91ed65d1..0fb94b6ff 100644 --- a/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php +++ b/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php @@ -1,6 +1,6 @@ Response::HTTP_NOT_FOUND, - ]; - } + public function __invoke(): JsonResponse + { + /** @var CoursesCounterResponse $response */ + $response = $this->ask(new FindCoursesCounterQuery()); - public function __invoke() - { - /** @var CoursesCounterResponse $response */ - $response = $this->ask(new FindCoursesCounterQuery()); + return new JsonResponse( + [ + 'total' => $response->total(), + ] + ); + } - return new JsonResponse( - [ - 'total' => $response->total(), - ] - ); - } + protected function exceptions(): array + { + return [ + CoursesCounterNotExist::class => Response::HTTP_NOT_FOUND, + ]; + } } diff --git a/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php b/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php index e267ac7be..1c20feae2 100644 --- a/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php +++ b/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php @@ -1,6 +1,6 @@ generator = $generator; - } - - public function __invoke(Request $request): JsonResponse - { - return new JsonResponse( - [ - 'mooc-backend' => 'ok', - 'rand' => $this->generator->generate(), - ] - ); - } + public function __invoke(Request $request): JsonResponse + { + return new JsonResponse( + [ + 'mooc-backend' => 'ok', + 'rand' => $this->generator->generate(), + ] + ); + } } diff --git a/apps/mooc/backend/src/Controller/Metrics/MetricsController.php b/apps/mooc/backend/src/Controller/Metrics/MetricsController.php new file mode 100644 index 000000000..9db1cebc2 --- /dev/null +++ b/apps/mooc/backend/src/Controller/Metrics/MetricsController.php @@ -0,0 +1,23 @@ +render($this->monitor->registry()->getMetricFamilySamples()); + + return new Response($result, 200, ['Content-Type' => RenderTextFormat::MIME_TYPE]); + } +} diff --git a/apps/mooc/backend/src/MoocBackendKernel.php b/apps/mooc/backend/src/MoocBackendKernel.php index baa17590a..e0b82d39e 100644 --- a/apps/mooc/backend/src/MoocBackendKernel.php +++ b/apps/mooc/backend/src/MoocBackendKernel.php @@ -1,6 +1,6 @@ getProjectDir() . '/config/bundles.php'; - foreach ($contents as $class => $envs) { - if ($envs[$this->environment] ?? $envs['all'] ?? false) { - yield new $class(); - } - } - } - - public function getProjectDir(): string - { - return dirname(__DIR__); - } - - protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void - { - $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', true); - $confDir = $this->getProjectDir() . '/config'; - - $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); - $loader->load($confDir . '/services_' . $this->environment . self::CONFIG_EXTS, 'glob'); - $loader->load($confDir . '/services/*' . self::CONFIG_EXTS, 'glob'); - } - - protected function configureRoutes(RouteCollectionBuilder $routes): void - { - $confDir = $this->getProjectDir() . '/config'; - - $routes->import($confDir . '/{routes}/*' . self::CONFIG_EXTS, '/', 'glob'); - } + use MicroKernelTrait; + + private const string CONFIG_EXTS = '.{xml,yaml}'; + + public function registerBundles(): iterable + { + $contents = require $this->getProjectDir() . '/config/bundles.php'; + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } + + public function getProjectDir(): string + { + return dirname(__DIR__); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); + $container->setParameter('.container.dumper.inline_class_loader', true); + $confDir = $this->getProjectDir() . '/config'; + + $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/services_' . $this->environment . self::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/services/*' . self::CONFIG_EXTS, 'glob'); + } } diff --git a/tests/apps/mooc/backend/features/courses/course_put.feature b/apps/mooc/backend/tests/features/courses/course_put.feature similarity index 100% rename from tests/apps/mooc/backend/features/courses/course_put.feature rename to apps/mooc/backend/tests/features/courses/course_put.feature diff --git a/tests/apps/mooc/backend/features/courses_counter/courses_counter_get.feature b/apps/mooc/backend/tests/features/courses_counter/courses_counter_get.feature similarity index 100% rename from tests/apps/mooc/backend/features/courses_counter/courses_counter_get.feature rename to apps/mooc/backend/tests/features/courses_counter/courses_counter_get.feature diff --git a/tests/apps/mooc/backend/features/health_check/health_check_get.feature b/apps/mooc/backend/tests/features/health_check/health_check_get.feature similarity index 100% rename from tests/apps/mooc/backend/features/health_check/health_check_get.feature rename to apps/mooc/backend/tests/features/health_check/health_check_get.feature diff --git a/tests/apps/mooc/backend/mooc_backend.yml b/apps/mooc/backend/tests/mooc_backend.yml similarity index 79% rename from tests/apps/mooc/backend/mooc_backend.yml rename to apps/mooc/backend/tests/mooc_backend.yml index 094d5f85b..75681594e 100644 --- a/tests/apps/mooc/backend/mooc_backend.yml +++ b/apps/mooc/backend/tests/mooc_backend.yml @@ -12,18 +12,18 @@ mooc_backend: suites: health_check: - paths: [ tests/apps/mooc/backend/features/health_check ] + paths: [ apps/mooc/backend/tests/features/health_check ] contexts: - CodelyTv\Tests\Shared\Infrastructure\Behat\ApiContext courses: - paths: [ tests/apps/mooc/backend/features/courses ] + paths: [ apps/mooc/backend/tests/features/courses ] contexts: - CodelyTv\Tests\Shared\Infrastructure\Behat\ApplicationFeatureContext - CodelyTv\Tests\Shared\Infrastructure\Behat\ApiContext courses_counter: - paths: [ tests/apps/mooc/backend/features/courses_counter ] + paths: [ apps/mooc/backend/tests/features/courses_counter ] contexts: - CodelyTv\Tests\Shared\Infrastructure\Behat\ApplicationFeatureContext - CodelyTv\Tests\Shared\Infrastructure\Behat\ApiContext diff --git a/src/Mooc/Videos/Infrastructure/.gitkeep b/apps/mooc/backend/var/.gitkeep similarity index 100% rename from src/Mooc/Videos/Infrastructure/.gitkeep rename to apps/mooc/backend/var/.gitkeep diff --git a/tests/apps/backoffice/backend/.gitkeep b/apps/mooc/frontend/var/.gitkeep similarity index 100% rename from tests/apps/backoffice/backend/.gitkeep rename to apps/mooc/frontend/var/.gitkeep diff --git a/behat.yml b/behat.yml index 142855c34..af66723dd 100644 --- a/behat.yml +++ b/behat.yml @@ -1,2 +1,2 @@ imports: - - tests/apps/mooc/backend/mooc_backend.yml + - apps/mooc/backend/tests/mooc_backend.yml diff --git a/composer.json b/composer.json index 780debf81..52f696576 100644 --- a/composer.json +++ b/composer.json @@ -1,27 +1,10 @@ { - "name": "codelytv/ddd-skeleton", + "name": "codelytv/php-ddd-example", "license": "MIT", "type": "project", - "description": "DDD php Skeleton - Monorepository", - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/Behat/Behat", - "comment": "Waiting for new tag" - }, - { - "type": "vcs", - "url": "https://github.com/DonCallisto/MinkExtension", - "comment": "Waiting for https://github.com/Behat/MinkExtension/pull/355" - }, - { - "type": "vcs", - "url": "https://github.com/ruudk/MinkBrowserKitDriver", - "comment": "Waiting for https://github.com/minkphp/Mink/issues/787 and then a new release" - } - ], + "description": "An example project applying Domain-Driven Design, Hexagonal Architecture and CQRS in a Monorepository", "require": { - "php": "7.4", + "php": "^8.3", "ext-amqp": "*", "ext-apcu": "*", @@ -29,40 +12,56 @@ "ext-zend-opcache": "*", "ext-pdo": "*", - "symfony/framework-bundle": "^5.0", - "symfony/messenger": "^5.0", - "symfony/dotenv": "^5.0", - "symfony/yaml": "^5.0", - "symfony/twig-bundle": "^5.0", - "symfony/validator": "^5.0", + "symfony/framework-bundle": "^7", + "symfony/messenger": "^7", + "symfony/dotenv": "^7", + "symfony/yaml": "^7", + "symfony/twig-bundle": "^7", + "symfony/validator": "^7", + "symfony/cache": "^7", + + "lambdish/phunctional": "^2", + + "ramsey/uuid": "^4", - "lambdish/phunctional": "^2.0", + "doctrine/dbal": "^3", + "doctrine/orm": "^2", - "ramsey/uuid": "^3.8", + "ocramius/proxy-manager": "^2", + "laminas/laminas-zendframework-bridge": "^1", - "doctrine/dbal": "^2.9", - "doctrine/orm": "^2.6", - "ocramius/proxy-manager": "^2.6", + "elasticsearch/elasticsearch": "^7", + "monolog/monolog": "^3", - "elasticsearch/elasticsearch": "^7.3", - "monolog/monolog": "^2.0" + "promphp/prometheus_client_php": "^2.7.2" }, "require-dev": { "ext-xdebug": "*", "roave/security-advisories": "dev-master", - "behat/behat": "dev-master as 3.6", - "behat/mink-extension": "dev-patch-4", - "behat/mink-browserkit-driver": "dev-symfony-5", - "friends-of-behat/symfony-extension": "2.1.0-BETA.1", + "behat/behat": "^3.13", + "friends-of-behat/mink-extension": "2.7.5", + "friends-of-behat/symfony-extension": "2.6.0", + "behat/mink-browserkit-driver": "2.2.0", - "phpunit/phpunit": "^8.5", - "mockery/mockery": "^1.2", + "phpunit/phpunit": "^9", + "mockery/mockery": "^1", - "fzaninotto/faker": "^1.8", + "fakerphp/faker": "^1", - "symfony/debug": "^4.4" + "symfony/error-handler": "^7", + + "symplify/easy-coding-standard": "^12.0", + "vimeo/psalm": "^5.15", + "rector/rector": "^0.18.12", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-symfony": "^5.0", + "psalm/plugin-phpunit": "^0.18.4", + "phpstan/phpstan": "^1.10", + "phpat/phpat": "^0.10.10", + "phpmd/phpmd": "^2.14", + "codelytv/coding-style": "^1.1" }, "autoload": { "psr-4": { @@ -77,7 +76,13 @@ }, "autoload-dev": { "psr-4": { - "CodelyTv\\Tests\\": ["tests/src"] + "CodelyTv\\Tests\\": ["tests"] + } + }, + "minimum-stability": "RC", + "config": { + "allow-plugins": { + "ocramius/package-versions": true } } } diff --git a/composer.lock b/composer.lock index 0216e00ad..534d648f6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,112 +4,97 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6dab900f626c1c58c8f3784e13725b3d", + "content-hash": "1384ca0a67984f7a0296f15a4373fed1", "packages": [ { - "name": "doctrine/annotations", - "version": "v1.8.0", + "name": "brick/math", + "version": "0.12.1", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", - "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^7.1" + "php": "^8.1" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^7.5" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Brick\\Math\\": "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": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://github.com/BenMorel", + "type": "github" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2019-10-01T18:55:10+00:00" + "time": "2023-11-29T23:19:16+00:00" }, { "name": "doctrine/cache", - "version": "1.10.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62" + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62", - "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", "shasum": "" }, "require": { - "php": "~7.1" + "php": "~7.1 || ^8.0" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^6.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0", - "predis/predis": "~1.0" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + "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", - "extra": { - "branch-alias": { - "dev-master": "1.9.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" @@ -154,40 +139,56 @@ "redis", "xcache" ], - "time": "2019-11-29T15:36:20+00:00" + "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": "1.6.4", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7" + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7", - "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d8af7f248c74f195f7347424600fd9e17b57af59", + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59", "shasum": "" }, "require": { - "php": "^7.1.3" + "doctrine/deprecations": "^1", + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan-shim": "^0.9.2", - "phpunit/phpunit": "^7.0", - "vimeo/psalm": "^3.2.2" + "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", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + "Doctrine\\Common\\Collections\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -224,50 +225,58 @@ "iterators", "php" ], - "time": "2019-11-13T13:07:11+00:00" + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.2.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%2Fcollections", + "type": "tidelift" + } + ], + "time": "2024-04-18T06:56:21+00:00" }, { "name": "doctrine/common", - "version": "2.12.0", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "2053eafdf60c2172ee1373d1b9289ba1db7f1fc6" + "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/2053eafdf60c2172ee1373d1b9289ba1db7f1fc6", - "reference": "2053eafdf60c2172ee1373d1b9289ba1db7f1fc6", + "url": "https://api.github.com/repos/doctrine/common/zipball/0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", + "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", "shasum": "" }, "require": { - "doctrine/annotations": "^1.0", - "doctrine/cache": "^1.0", - "doctrine/collections": "^1.0", - "doctrine/event-manager": "^1.0", - "doctrine/inflector": "^1.0", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^1.1", - "doctrine/reflection": "^1.0", - "php": "^7.1" + "doctrine/persistence": "^2.0 || ^3.0", + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^1.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpunit/phpunit": "^7.0", + "doctrine/coding-standard": "^9.0 || ^10.0", + "doctrine/collections": "^1", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^4.0.5" + "symfony/phpunit-bridge": "^6.1", + "vimeo/psalm": "^4.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.11.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -300,41 +309,69 @@ "email": "ocramius@gmail.com" } ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.", + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", "homepage": "https://www.doctrine-project.org/projects/common.html", "keywords": [ "common", "doctrine", "php" ], - "time": "2020-01-10T15:49:25+00:00" + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.4.4" + }, + "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%2Fcommon", + "type": "tidelift" + } + ], + "time": "2024-04-16T13:35:33+00:00" }, { "name": "doctrine/dbal", - "version": "v2.10.1", + "version": "3.8.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8" + "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/b7411825cf7efb7e51f9791dea19d86e43b399a1", + "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1", "shasum": "" }, "require": { - "doctrine/cache": "^1.0", - "doctrine/event-manager": "^1.0", - "ext-pdo": "*", - "php": "^7.2" + "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": "^6.0", - "jetbrains/phpstorm-stubs": "^2019.1", - "phpstan/phpstan": "^0.11.3", - "phpunit/phpunit": "^8.4.1", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0" + "doctrine/coding-standard": "12.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.11.5", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "9.6.19", + "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.10.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." @@ -343,15 +380,9 @@ "bin/doctrine-dbal" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "3.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + "Doctrine\\DBAL\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -394,46 +425,107 @@ "queryobject", "sasql", "sql", - "sqlanywhere", "sqlite", "sqlserver", "sqlsrv" ], - "time": "2020-01-04T12:56:21+00:00" + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.8.6" + }, + "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-06-19T10:38:17+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/event-manager", - "version": "1.1.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "629572819973f13486371cb611386eb17851e85c" + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/629572819973f13486371cb611386eb17851e85c", - "reference": "629572819973f13486371cb611386eb17851e85c", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^8.1" }, "conflict": { - "doctrine/common": "<2.9@dev" + "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.0" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -475,37 +567,55 @@ "event system", "events" ], - "time": "2019-11-10T09:48:07+00:00" + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.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%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2024-05-22T20:47:39+00:00" }, { "name": "doctrine/inflector", - "version": "1.3.1", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "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", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -534,48 +644,68 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "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", - "pluralize", - "singularize", - "string" + "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": "2019-10-30T19:59:35+00:00" + "time": "2024-02-18T20:23:39+00:00" }, { "name": "doctrine/instantiator", - "version": "1.3.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^6.0", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-shim": "^0.11", - "phpunit/phpunit": "^7.0" + "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", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -589,7 +719,7 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", @@ -598,39 +728,54 @@ "constructor", "instantiate" ], - "time": "2019-10-21T16:45:58+00:00" + "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": "1.2.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", - "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "php": "^7.2" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -660,55 +805,87 @@ "parser", "php" ], - "time": "2019-10-30T14:39:59+00:00" + "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/orm", - "version": "v2.7.0", + "version": "2.19.6", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "4d763ca4c925f647b248b9fa01b5f47aa3685d62" + "reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/4d763ca4c925f647b248b9fa01b5f47aa3685d62", - "reference": "4d763ca4c925f647b248b9fa01b5f47aa3685d62", + "url": "https://api.github.com/repos/doctrine/orm/zipball/c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073", + "reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073", "shasum": "" }, "require": { - "doctrine/annotations": "^1.8", - "doctrine/cache": "^1.9.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^2.11", - "doctrine/dbal": "^2.9.3", - "doctrine/event-manager": "^1.1", - "doctrine/instantiator": "^1.3", - "doctrine/persistence": "^1.2", - "ext-pdo": "*", - "php": "^7.1", - "symfony/console": "^3.0|^4.0|^5.0" + "composer-runtime-api": "^2", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5 || ^2.1", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", + "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": "^2 || ^3", + "doctrine/persistence": "^2.4 || ^3", + "ext-ctype": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 3.0" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "phpunit/phpunit": "^7.5", - "symfony/yaml": "^3.4|^4.0|^5.0" + "doctrine/annotations": "^1.13 || ^2", + "doctrine/coding-standard": "^9.0.2 || ^12.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "~1.4.10 || 1.11.1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "psr/log": "^1 || ^2 || ^3", + "squizlabs/php_codesniffer": "3.7.2", + "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "vimeo/psalm": "4.30.0 || 5.24.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", "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" }, "bin": [ "bin/doctrine" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" + "Doctrine\\ORM\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -743,48 +920,48 @@ "database", "orm" ], - "time": "2019-11-19T08:38:05+00:00" + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/2.19.6" + }, + "time": "2024-06-26T17:24:40+00:00" }, { "name": "doctrine/persistence", - "version": "1.3.6", + "version": "3.3.3", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "5dd3ac5eebef2d0b074daa4440bb18f93132dee4" + "reference": "b337726451f5d530df338fc7f68dee8781b49779" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/5dd3ac5eebef2d0b074daa4440bb18f93132dee4", - "reference": "5dd3ac5eebef2d0b074daa4440bb18f93132dee4", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/b337726451f5d530df338fc7f68dee8781b49779", + "reference": "b337726451f5d530df338fc7f68dee8781b49779", "shasum": "" }, "require": { - "doctrine/annotations": "^1.0", - "doctrine/cache": "^1.0", - "doctrine/collections": "^1.0", - "doctrine/event-manager": "^1.0", - "doctrine/reflection": "^1.1", - "php": "^7.1" + "doctrine/event-manager": "^1 || ^2", + "php": "^7.2 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" }, "conflict": { - "doctrine/common": "<2.10@dev" + "doctrine/common": "<2.10" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11", - "phpunit/phpunit": "^7.0" + "doctrine/coding-standard": "^12", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.11.1", + "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.24.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common", - "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" + "Doctrine\\Persistence\\": "src/Persistence" } }, "notification-url": "https://packagist.org/downloads/", @@ -818,7 +995,7 @@ } ], "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", - "homepage": "https://doctrine-project.org/projects/persistence.html", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", "keywords": [ "mapper", "object", @@ -826,115 +1003,54 @@ "orm", "persistence" ], - "time": "2020-01-16T22:06:23+00:00" - }, - { - "name": "doctrine/reflection", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/reflection.git", - "reference": "bc420ead87fdfe08c03ecc3549db603a45b06d4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/reflection/zipball/bc420ead87fdfe08c03ecc3549db603a45b06d4c", - "reference": "bc420ead87fdfe08c03ecc3549db603a45b06d4c", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.0", - "ext-tokenizer": "*", - "php": "^7.1" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^5.0", - "doctrine/common": "^2.10", - "phpstan/phpstan": "^0.11.0", - "phpstan/phpstan-phpunit": "^0.11.0", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/3.3.3" }, - "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" - }, + "funding": [ { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" }, { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" } ], - "description": "The Doctrine Reflection project is a simple library used by the various Doctrine projects which adds some additional functionality on top of the reflection functionality that comes with PHP. It allows you to get the reflection information about classes, methods and properties statically.", - "homepage": "https://www.doctrine-project.org/projects/reflection.html", - "keywords": [ - "reflection", - "static" - ], - "time": "2020-01-08T19:53:19+00:00" + "time": "2024-06-20T10:14:30+00:00" }, { "name": "elasticsearch/elasticsearch", - "version": "v7.5.0", + "version": "v7.17.2", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "81fbecb1046b801729fc10e2c494fffd683b0518" + "reference": "2d302233f2bb0926812d82823bb820d405e130fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/81fbecb1046b801729fc10e2c494fffd683b0518", - "reference": "81fbecb1046b801729fc10e2c494fffd683b0518", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/2d302233f2bb0926812d82823bb820d405e130fc", + "reference": "2d302233f2bb0926812d82823bb820d405e130fc", "shasum": "" }, "require": { "ext-json": ">=1.3.7", - "guzzlehttp/ringphp": "~1.0", - "php": "^7.1", - "psr/log": "~1.0" + "ezimuel/ringphp": "^1.1.2", + "php": "^7.3 || ^8.0", + "psr/log": "^1|^2|^3" }, "require-dev": { - "cpliakas/git-wrapper": "~2.0", - "doctrine/inflector": "^1.3", + "ext-yaml": "*", + "ext-zip": "*", "mockery/mockery": "^1.2", - "phpstan/phpstan-shim": "^0.11", - "phpunit/phpunit": "^7.5", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.4", - "symfony/finder": "~4.0", - "symfony/yaml": "~4.0" + "symfony/finder": "~4.0" }, "suggest": { "ext-curl": "*", @@ -951,7 +1067,8 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "Apache-2.0", + "LGPL-2.1-only" ], "authors": [ { @@ -967,43 +1084,41 @@ "elasticsearch", "search" ], - "time": "2019-12-19T15:48:35+00:00" + "support": { + "issues": "https://github.com/elastic/elasticsearch-php/issues", + "source": "https://github.com/elastic/elasticsearch-php/tree/v7.17.2" + }, + "time": "2023-04-21T15:31:12+00:00" }, { - "name": "guzzlehttp/ringphp", - "version": "1.1.1", + "name": "ezimuel/guzzlestreams", + "version": "3.1.0", "source": { "type": "git", - "url": "https://github.com/guzzle/RingPHP.git", - "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b" + "url": "https://github.com/ezimuel/guzzlestreams.git", + "reference": "b4b5a025dfee70d6cd34c780e07330eb93d5b997" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b", - "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b", + "url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/b4b5a025dfee70d6cd34c780e07330eb93d5b997", + "reference": "b4b5a025dfee70d6cd34c780e07330eb93d5b997", "shasum": "" }, "require": { - "guzzlehttp/streams": "~3.0", - "php": ">=5.4.0", - "react/promise": "~2.0" + "php": ">=5.4.0" }, "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" + "phpunit/phpunit": "~9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { - "GuzzleHttp\\Ring\\": "src/" + "GuzzleHttp\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1017,39 +1132,55 @@ "homepage": "https://github.com/mtdowling" } ], - "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", - "abandoned": true, - "time": "2018-07-31T13:22:33+00:00" + "description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "stream" + ], + "support": { + "source": "https://github.com/ezimuel/guzzlestreams/tree/3.1.0" + }, + "time": "2022-10-24T12:58:50+00:00" }, { - "name": "guzzlehttp/streams", - "version": "3.0.0", + "name": "ezimuel/ringphp", + "version": "1.2.2", "source": { "type": "git", - "url": "https://github.com/guzzle/streams.git", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + "url": "https://github.com/ezimuel/ringphp.git", + "reference": "7887fc8488013065f72f977dcb281994f5fde9f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "url": "https://api.github.com/repos/ezimuel/ringphp/zipball/7887fc8488013065f72f977dcb281994f5fde9f4", + "reference": "7887fc8488013065f72f977dcb281994f5fde9f4", "shasum": "" }, "require": { - "php": ">=5.4.0" + "ezimuel/guzzlestreams": "^3.0.1", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "replace": { + "guzzlehttp/ringphp": "self.version" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "ext-curl": "*", + "phpunit/phpunit": "~9.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.1-dev" } }, "autoload": { "psr-4": { - "GuzzleHttp\\Stream\\": "src/" + "GuzzleHttp\\Ring\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1063,27 +1194,24 @@ "homepage": "https://github.com/mtdowling" } ], - "description": "Provides a simple abstraction over streams of data", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "stream" - ], - "abandoned": true, - "time": "2014-10-12T19:18:40+00:00" + "description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php", + "support": { + "source": "https://github.com/ezimuel/ringphp/tree/1.2.2" + }, + "time": "2022-12-07T11:28:53+00:00" }, { "name": "lambdish/phunctional", - "version": "v2.0.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/Lambdish/phunctional.git", - "reference": "d21bf670ef4e1bd5ba7fc94b6cd2ae30053d21b4" + "reference": "ed3482e7da134d886789abb33c6df22a5d2f271c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Lambdish/phunctional/zipball/d21bf670ef4e1bd5ba7fc94b6cd2ae30053d21b4", - "reference": "d21bf670ef4e1bd5ba7fc94b6cd2ae30053d21b4", + "url": "https://api.github.com/repos/Lambdish/phunctional/zipball/ed3482e7da134d886789abb33c6df22a5d2f271c", + "reference": "ed3482e7da134d886789abb33c6df22a5d2f271c", "shasum": "" }, "require": { @@ -1105,16 +1233,13 @@ ], "authors": [ { - "name": "Eloi Poch", - "email": "eloi.poch@gmail.com" + "name": "Eloi Poch" }, { - "name": "Jorge Ávila", - "email": "avilacardenosa@gmail.com" + "name": "Jorge Ávila" }, { - "name": "Rafa GΓ³mez", - "email": "rgomezcasas@gmail.com" + "name": "Rafa GΓ³mez" } ], "description": "Ξ» PHP functional library", @@ -1125,52 +1250,43 @@ "library", "php" ], - "time": "2019-12-08T23:46:38+00:00" + "support": { + "issues": "https://github.com/Lambdish/phunctional/issues", + "source": "https://github.com/Lambdish/phunctional/tree/v2.1.0" + }, + "time": "2020-09-18T07:22:08+00:00" }, { "name": "laminas/laminas-code", - "version": "3.4.1", + "version": "4.14.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "1cb8f203389ab1482bf89c0e70a04849bacd7766" + "reference": "562e02b7d85cb9142b5116cc76c4c7c162a11a1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/1cb8f203389ab1482bf89c0e70a04849bacd7766", - "reference": "1cb8f203389ab1482bf89c0e70a04849bacd7766", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/562e02b7d85cb9142b5116cc76c4c7c162a11a1c", + "reference": "562e02b7d85cb9142b5116cc76c4c7c162a11a1c", "shasum": "" }, "require": { - "laminas/laminas-eventmanager": "^2.6 || ^3.0", - "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^7.1" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0" - }, - "replace": { - "zendframework/zend-code": "self.version" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { - "doctrine/annotations": "^1.7", + "doctrine/annotations": "^2.0.1", "ext-phar": "*", - "laminas/laminas-coding-standard": "^1.0", - "laminas/laminas-stdlib": "^2.7 || ^3.0", - "phpunit/phpunit": "^7.5.16 || ^8.4" + "laminas/laminas-coding-standard": "^2.5.0", + "laminas/laminas-stdlib": "^3.17.0", + "phpunit/phpunit": "^10.3.3", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.15.0" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4.x-dev", - "dev-develop": "3.5.x-dev", - "dev-dev-4.0": "4.0.x-dev" - } - }, "autoload": { "psr-4": { "Laminas\\Code\\": "src/" @@ -1184,95 +1300,50 @@ "homepage": "https://laminas.dev", "keywords": [ "code", - "laminas" + "laminas", + "laminasframework" ], - "time": "2019-12-31T16:28:24+00:00" - }, - { - "name": "laminas/laminas-eventmanager", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-eventmanager.git", - "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", - "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", - "shasum": "" - }, - "require": { - "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^5.6 || ^7.0" - }, - "replace": { - "zendframework/zend-eventmanager": "self.version" - }, - "require-dev": { - "athletic/athletic": "^0.1", - "container-interop/container-interop": "^1.1.0", - "laminas/laminas-coding-standard": "~1.0.0", - "laminas/laminas-stdlib": "^2.7.3 || ^3.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" - }, - "suggest": { - "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", - "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" - } + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-code/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-code/issues", + "rss": "https://github.com/laminas/laminas-code/releases.atom", + "source": "https://github.com/laminas/laminas-code" }, - "autoload": { - "psr-4": { - "Laminas\\EventManager\\": "src/" + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" ], - "description": "Trigger and listen to events within a PHP application", - "homepage": "https://laminas.dev", - "keywords": [ - "event", - "eventmanager", - "events", - "laminas" - ], - "time": "2019-12-31T16:44:52+00:00" + "time": "2024-06-17T08:50:25+00:00" }, { "name": "laminas/laminas-zendframework-bridge", - "version": "1.0.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-zendframework-bridge.git", - "reference": "0fb9675b84a1666ab45182b6c5b29956921e818d" + "reference": "eb0d96c708b92177a92bc2239543d3ed523452c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/0fb9675b84a1666ab45182b6c5b29956921e818d", - "reference": "0fb9675b84a1666ab45182b6c5b29956921e818d", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/eb0d96c708b92177a92bc2239543d3ed523452c6", + "reference": "eb0d96c708b92177a92bc2239543d3ed523452c6", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "^10.4", + "psalm/plugin-phpunit": "^0.18.0", + "squizlabs/php_codesniffer": "^3.7.1", + "vimeo/psalm": "^5.16.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev", - "dev-develop": "1.1.x-dev" - }, "laminas": { "module": "Laminas\\ZendFrameworkBridge" } @@ -1296,62 +1367,81 @@ "laminas", "zf" ], - "time": "2020-01-07T22:58:31+00:00" + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "abandoned": true, + "time": "2023-11-24T13:56:19+00:00" }, { "name": "monolog/monolog", - "version": "2.0.2", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", "shasum": "" }, "require": { - "php": "^7.2", - "psr/log": "^1.0.1" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^6.0", - "graylog2/gelf-php": "^1.4.2", - "jakub-onderka/php-parallel-lint": "^0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", - "phpunit/phpunit": "^8.3", - "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <3.0", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "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.5.17", + "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", - "php-console/php-console": "Allow sending log messages to Google Chrome", "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-master": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -1367,106 +1457,64 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", "psr-3" ], - "time": "2019-12-20T14:22:59+00:00" - }, - { - "name": "ocramius/package-versions", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "1d32342b8c1eb27353c8887c366147b4c2da673c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/1d32342b8c1eb27353c8887c366147b4c2da673c", - "reference": "1d32342b8c1eb27353c8887c366147b4c2da673c", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0.0", - "php": "^7.3.0" - }, - "require-dev": { - "composer/composer": "^1.8.6", - "doctrine/coding-standard": "^6.0.0", - "ext-zip": "*", - "infection/infection": "^0.13.4", - "phpunit/phpunit": "^8.2.5", - "vimeo/psalm": "^3.4.9" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" } ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2019-07-17T15:49:50+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "ocramius/proxy-manager", - "version": "2.6.1", + "version": "2.14.1", "source": { "type": "git", "url": "https://github.com/Ocramius/ProxyManager.git", - "reference": "dec37bfb3c3594440ee4fa263494189344787d22" + "reference": "3990d60ef79001badbab4927a6a811682274a0d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/dec37bfb3c3594440ee4fa263494189344787d22", - "reference": "dec37bfb3c3594440ee4fa263494189344787d22", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/3990d60ef79001badbab4927a6a811682274a0d1", + "reference": "3990d60ef79001badbab4927a6a811682274a0d1", "shasum": "" }, "require": { - "laminas/laminas-code": "^3.4.1", - "ocramius/package-versions": "^1.5.1", - "php": "7.4.*", - "webimpress/safe-writer": "^2.0" + "composer-runtime-api": "^2.1.0", + "laminas/laminas-code": "^4.4.2", + "php": "~8.0.0", + "webimpress/safe-writer": "^2.2.0" }, "conflict": { - "doctrine/annotations": "<1.6.1", - "laminas/laminas-stdlib": "<3.2.1", - "zendframework/zend-stdlib": "<3.2.1" + "thecodingmachine/safe": "<1.3.3" }, "require-dev": { - "doctrine/coding-standard": "^6.0.0", + "codelicia/xulieta": "^0.1.6", + "doctrine/coding-standard": "^9.0.0", "ext-phar": "*", - "infection/infection": "^0.15.0", - "mikey179/vfsstream": "^1.6.8", - "nikic/php-parser": "^4.3.0", - "phpbench/phpbench": "^0.16.10", - "phpunit/phpunit": "^8.5.1", - "slevomat/coding-standard": "^5.0.4", - "squizlabs/php_codesniffer": "^3.5.3", - "symfony/console": "^4.4.2", - "vimeo/psalm": "3.7.0" + "phpbench/phpbench": "^1.0.3", + "phpunit/phpunit": "^9.5.6", + "roave/infection-static-analysis-plugin": "^1.8", + "squizlabs/php_codesniffer": "^3.6.0", + "vimeo/psalm": "^4.8.1" }, "suggest": { "laminas/laminas-json": "To have the JsonRpc adapter (Remote Object feature)", @@ -1475,11 +1523,6 @@ "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, "autoload": { "psr-4": { "ProxyManager\\": "src/ProxyManager" @@ -1493,7 +1536,7 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.io/" + "homepage": "https://ocramius.github.io/" } ], "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", @@ -1505,69 +1548,106 @@ "proxy pattern", "service proxies" ], - "time": "2020-01-27T09:25:51+00:00" + "support": { + "issues": "https://github.com/Ocramius/ProxyManager/issues", + "source": "https://github.com/Ocramius/ProxyManager/tree/2.14.1" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", + "type": "tidelift" + } + ], + "time": "2022-03-05T18:43:14+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "promphp/prometheus_client_php", + "version": "v2.11.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/PromPHP/prometheus_client_php.git", + "reference": "35d5a68628ea18209938bc1b8796646015ab93cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/PromPHP/prometheus_client_php/zipball/35d5a68628ea18209938bc1b8796646015ab93cf", + "reference": "35d5a68628ea18209938bc1b8796646015ab93cf", "shasum": "" }, "require": { - "php": "^7" + "ext-json": "*", + "php": "^7.2|^8.0" + }, + "replace": { + "endclothing/prometheus_client_php": "*", + "jimdo/prometheus_client_php": "*", + "lkaemmerling/prometheus_client_php": "*" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" + "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-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "ext-apc": "Required if using APCu.", + "ext-pdo": "Required if using PDO.", + "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": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "name": "Lukas KΓ€mmerling", + "email": "kontakt@lukas-kaemmerling.de" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "time": "2018-07-02T15:55:56+00:00" + "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.11.0" + }, + "time": "2024-08-05T07:58:08+00:00" }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -1587,7 +1667,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -1596,29 +1676,80 @@ "psr", "psr-6" ], - "time": "2016-08-06T20:24:11+00:00" + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "psr/container", + "name": "psr/clock", "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1633,7 +1764,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -1645,7 +1776,11 @@ "container-interop", "psr" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -1691,34 +1826,38 @@ "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": "1.1.2", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1728,7 +1867,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -1738,66 +1877,63 @@ "psr", "psr-3" ], - "time": "2019-11-01T11:05:21+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" }, { - "name": "ramsey/uuid", - "version": "3.9.2", + "name": "ramsey/collection", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "7779489a47d443f845271badbdcedfe4df8e06fb" + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/7779489a47d443f845271badbdcedfe4df8e06fb", - "reference": "7779489a47d443f845271badbdcedfe4df8e06fb", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", "shasum": "" }, "require": { - "ext-json": "*", - "paragonie/random_compat": "^1 | ^2 | 9.99.99", - "php": "^5.4 | ^7 | ^8", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" + "php": "^8.1" }, "require-dev": { - "codeception/aspect-mock": "^1 | ^2", - "doctrine/annotations": "^1.2", - "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", - "jakub-onderka/php-parallel-lint": "^1", - "mockery/mockery": "^0.9.11 | ^1", - "moontoast/math": "^1.1", - "paragonie/random-lib": "^2", - "php-mock/php-mock-phpunit": "^0.3 | ^1.1", - "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", - "squizlabs/php_codesniffer": "^3.5" - }, - "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" } }, "autoload": { "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, - "files": [ - "src/functions.php" - ] + "Ramsey\\Collection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1808,53 +1944,153 @@ "name": "Ben Ramsey", "email": "ben@benramsey.com", "homepage": "https://benramsey.com" - }, + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" + "url": "https://github.com/ramsey", + "type": "github" }, { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" } ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "time": "2019-12-17T08:18:51+00:00" + "time": "2022-12-31T21:50:55+00:00" }, { - "name": "react/promise", - "version": "v2.7.1", + "name": "ramsey/uuid", + "version": "4.7.6", "source": { "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", "shasum": "" }, "require": { - "php": ">=5.4.0" + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "React\\Promise\\": "src/" + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, + { + "name": "react/promise", + "version": "v2.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1863,7 +2099,23 @@ "authors": [ { "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian LΓΌck", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], "description": "A lightweight implementation of CommonJS Promises/A for PHP", @@ -1871,61 +2123,72 @@ "promise", "promises" ], - "time": "2019-01-07T21:25:54+00:00" + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-16T16:16:50+00:00" }, { "name": "symfony/cache", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "b503e72c8e2fa55eed9e2d3dd6a166f3eaaabb9a" + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b503e72c8e2fa55eed9e2d3dd6a166f3eaaabb9a", - "reference": "b503e72c8e2fa55eed9e2d3dd6a166f3eaaabb9a", + "url": "https://api.github.com/repos/symfony/cache/zipball/8ac37acee794372f9732fe8a61a8221f6762148e", + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/cache": "~1.0", - "psr/log": "~1.0", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/service-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { - "doctrine/dbal": "<2.5", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" }, "provide": { - "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0", - "symfony/cache-implementation": "1.0" + "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/cache": "~1.6", - "doctrine/dbal": "~2.5", - "predis/predis": "~1.1", - "psr/simple-cache": "^1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] @@ -1944,39 +2207,57 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], - "time": "2020-01-10T21:57:37+00:00" + "support": { + "source": "https://github.com/symfony/cache/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.0.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16" + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", - "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/cache": "^1.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.1", + "psr/cache": "^3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2008,49 +2289,54 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.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-04-18T09:32:20+00:00" }, { - "name": "symfony/config", - "version": "v5.0.3", + "name": "symfony/clock", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "7640c6704f56bf64045066bc5d93fd9d664baa63" + "url": "https://github.com/symfony/clock.git", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/7640c6704f56bf64045066bc5d93fd9d664baa63", - "reference": "7640c6704f56bf64045066bc5d93fd9d664baa63", + "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/filesystem": "^4.4|^5.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/finder": "<4.4" - }, - "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/yaml": "^4.4|^5.0" + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "provide": { + "psr/clock-implementation": "1.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { + "files": [ + "Resources/now.php" + ], "psr-4": { - "Symfony\\Component\\Config\\": "" + "Symfony\\Component\\Clock\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2062,71 +2348,75 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Decouples applications from the system clock", "homepage": "https://symfony.com", - "time": "2020-01-04T14:08:26+00:00" + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/console", - "version": "v5.0.3", + "name": "symfony/config", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "345ab6ecb456b5147ea3b3271d7f1f00aadfd257" + "url": "https://github.com/symfony/config.git", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/345ab6ecb456b5147ea3b3271d7f1f00aadfd257", - "reference": "345ab6ecb456b5147ea3b3271d7f1f00aadfd257", + "url": "https://api.github.com/repos/symfony/config/zipball/2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1|^2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/dependency-injection": "<4.4", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0" + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\Config\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2146,60 +2436,74 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", - "time": "2020-01-19T11:13:19+00:00" + "support": { + "source": "https://github.com/symfony/config/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v5.0.3", + "name": "symfony/console", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "5a56807650c7258bbcc4a15a020904958c70247e" + "url": "https://github.com/symfony/console.git", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5a56807650c7258bbcc4a15a020904958c70247e", - "reference": "5a56807650c7258bbcc4a15a020904958c70247e", + "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/container": "^1.0", - "symfony/service-contracts": "^1.1.6|^2" + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" }, "conflict": { - "symfony/config": "<5.0", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "symfony/config": "^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" + "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2219,39 +2523,73 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", - "time": "2020-01-21T08:40:24+00:00" + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" }, { - "name": "symfony/dotenv", - "version": "v5.0.3", + "name": "symfony/dependency-injection", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/dotenv.git", - "reference": "8331da80cc35fe903db0ff142376d518804ff1b1" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "8126f0be4ff984e4db0140e60917900a53facb49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/8331da80cc35fe903db0ff142376d518804ff1b1", - "reference": "8331da80cc35fe903db0ff142376d518804ff1b1", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8126f0be4ff984e4db0140e60917900a53facb49", + "reference": "8126f0be4ff984e4db0140e60917900a53facb49", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "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/process": "^4.4|^5.0" + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\Dotenv\\": "" + "Symfony\\Component\\DependencyInjection\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2271,50 +2609,57 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Registers environment variables from a .env file", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", - "keywords": [ - "dotenv", - "env", - "environment" + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2020-01-08T17:33:29+00:00" + "time": "2024-07-26T07:35:39+00:00" }, { - "name": "symfony/error-handler", - "version": "v5.0.3", + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", "source": { "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "95f64c1d7dfb86a722dc9d278d0edf5176eff16e" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/95f64c1d7dfb86a722dc9d278d0edf5176eff16e", - "reference": "95f64c1d7dfb86a722dc9d278d0edf5176eff16e", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/log": "^1.0", - "symfony/var-dumper": "^4.4|^5.0" - }, - "require-dev": { - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2323,65 +2668,64 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ErrorHandler Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "time": "2020-01-08T17:33:29+00:00" + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.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-04-18T09:32:20+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v5.0.3", + "name": "symfony/dotenv", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4a7a8cdca1120c091b4797f0e5bba69c1e783224" + "url": "https://github.com/symfony/dotenv.git", + "reference": "a26be30fd61678dab694a18a85084cea7673bbf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4a7a8cdca1120c091b4797f0e5bba69c1e783224", - "reference": "4a7a8cdca1120c091b4797f0e5bba69c1e783224", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/a26be30fd61678dab694a18a85084cea7673bbf3", + "reference": "a26be30fd61678dab694a18a85084cea7673bbf3", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/event-dispatcher-contracts": "^2" + "php": ">=8.2" }, "conflict": { - "symfony/dependency-injection": "<4.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/console": "<6.4", + "symfony/process": "<6.4" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" + "Symfony\\Component\\Dotenv\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2401,35 +2745,213 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Registers environment variables from a .env file", "homepage": "https://symfony.com", - "time": "2020-01-10T21:57:37+00:00" + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-09T19:36:07+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "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.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T13:02:51+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "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.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.0.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "af23c2584d4577d54661c434446fb8fbed6025dd" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd", - "reference": "af23c2584d4577d54661c434446fb8fbed6025dd", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2461,32 +2983,48 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.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-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v5.0.3", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3afadc0f57cd74f86379d073e694b0f2cda2a88c" + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3afadc0f57cd74f86379d073e694b0f2cda2a88c", - "reference": "3afadc0f57cd74f86379d073e694b0f2cda2a88c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } + "require-dev": { + "symfony/process": "^6.4|^7.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -2509,33 +3047,48 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "time": "2020-01-21T08:40:24+00:00" + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.1.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": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/finder", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4176e7cb846fe08f32518b7e0ed8462e2db8d9bb" + "reference": "717c6329886f32dc65e27461f80f2a465412fdca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4176e7cb846fe08f32518b7e0ed8462e2db8d9bb", - "reference": "4176e7cb846fe08f32518b7e0ed8462e2db8d9bb", + "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", + "reference": "717c6329886f32dc65e27461f80f2a465412fdca", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=8.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -2558,114 +3111,131 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "time": "2020-01-04T14:08:26+00:00" + "support": { + "source": "https://github.com/symfony/finder/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-24T07:08:44+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "cc59bc797b77c6ecccd6c87d9052f6b3eb6a8d63" + "reference": "a32ec544bd501eb4619eb977860ad3076ee55061" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/cc59bc797b77c6ecccd6c87d9052f6b3eb6a8d63", - "reference": "cc59bc797b77c6ecccd6c87d9052f6b3eb6a8d63", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a32ec544bd501eb4619eb977860ad3076ee55061", + "reference": "a32ec544bd501eb4619eb977860ad3076ee55061", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": "^7.2.5", - "symfony/cache": "^4.4|^5.0", - "symfony/config": "^5.0", - "symfony/dependency-injection": "^5.0.1", - "symfony/error-handler": "^4.4.1|^5.0.1", - "symfony/filesystem": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^7.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "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": "^5.0" + "symfony/routing": "^6.4|^7.0" }, "conflict": { "doctrine/persistence": "<1.3", - "phpdocumentor/reflection-docblock": "<3.0", - "phpdocumentor/type-resolver": "<0.2.1", - "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<4.4", - "symfony/browser-kit": "<4.4", - "symfony/console": "<4.4", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<4.4", - "symfony/form": "<4.4", - "symfony/http-client": "<4.4", - "symfony/lock": "<4.4", - "symfony/mailer": "<4.4", - "symfony/messenger": "<4.4", - "symfony/mime": "<4.4", - "symfony/property-info": "<4.4", - "symfony/serializer": "<4.4", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<4.4", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<4.4" + "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/annotations": "~1.7", - "doctrine/cache": "~1.0", - "paragonie/sodium_compat": "^1.8", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/asset": "^4.4|^5.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/dotenv": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/form": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/mailer": "^4.4|^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", + "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": "^4.4|^5.0", - "symfony/property-info": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-http": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/string": "~5.0.0", - "symfony/translation": "^5.0", - "symfony/twig-bundle": "^4.4|^5.0", - "symfony/validator": "^4.4|^5.0", - "symfony/web-link": "^4.4|^5.0", - "symfony/workflow": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0", - "twig/twig": "^2.10|^3.0" - }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/property-info": "For using the property_info service", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", - "symfony/yaml": "For using the debug:config and lint:yaml commands" + "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/type-info": "^7.1", + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" @@ -2688,39 +3258,61 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony FrameworkBundle", + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", - "time": "2020-01-21T08:40:24+00:00" + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T13:24:34+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.0.7", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "26fb006a2c7b6cdd23d52157b05f8414ffa417b6" + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/26fb006a2c7b6cdd23d52157b05f8414ffa417b6", - "reference": "26fb006a2c7b6cdd23d52157b05f8414ffa417b6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/mime": "^4.4|^5.0", - "symfony/polyfill-mbstring": "~1.1" + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/expression-language": "^4.4|^5.0" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" @@ -2743,80 +3335,98 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpFoundation Component", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", - "time": "2020-03-30T14:14:32+00:00" + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9e31e5e11cbe038cbb853beb3e3bb6e4f2500259" + "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9e31e5e11cbe038cbb853beb3e3bb6e4f2500259", - "reference": "9e31e5e11cbe038cbb853beb3e3bb6e4f2500259", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db9702f3a04cc471ec8c70e881825db26ac5f186", + "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/log": "~1.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/event-dispatcher": "^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^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": "<4.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/dependency-injection": "<4.4", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", - "twig/twig": "<2.4" + "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" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/cache": "~1.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/config": "^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.4|^3.0" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" + "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": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "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-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.0.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpKernel\\": "" @@ -2839,58 +3449,70 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpKernel Component", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", - "time": "2020-01-21T13:29:58+00:00" + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T14:58:15+00:00" }, { "name": "symfony/messenger", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "15359794dcadcc90f0713c3506e03187ea89117e" + "reference": "604e182a7758ceea35921a8ad5dd492a6e13bae4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/15359794dcadcc90f0713c3506e03187ea89117e", - "reference": "15359794dcadcc90f0713c3506e03187ea89117e", + "url": "https://api.github.com/repos/symfony/messenger/zipball/604e182a7758ceea35921a8ad5dd492a6e13bae4", + "reference": "604e182a7758ceea35921a8ad5dd492a6e13bae4", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/log": "~1.0" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/clock": "^6.4|^7.0" }, "conflict": { - "doctrine/persistence": "<1.3", - "symfony/event-dispatcher": "<4.4", - "symfony/framework-bundle": "<4.4", - "symfony/http-kernel": "<4.4" + "symfony/console": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/framework-bundle": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { - "doctrine/dbal": "^2.6", - "doctrine/persistence": "^1.3", - "psr/cache": "~1.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/validator": "^4.4|^5.0" - }, - "suggest": { - "enqueue/messenger-adapter": "For using the php-enqueue library as a transport." + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\": "" @@ -2913,49 +3535,64 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Messenger Component", + "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", - "time": "2020-01-21T10:12:04+00:00" + "support": { + "source": "https://github.com/symfony/messenger/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-09T19:36:07+00:00" }, { - "name": "symfony/mime", - "version": "v5.0.7", + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "481b7d6da88922fb1e0d86a943987722b08f3955" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/481b7d6da88922fb1e0d86a943987722b08f3955", - "reference": "481b7d6da88922fb1e0d86a943987722b08f3955", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" + "php": ">=7.1" }, - "conflict": { - "symfony/mailer": "<4.4" + "provide": { + "ext-ctype": "*" }, - "require-dev": { - "egulias/email-validator": "^2.1.10", - "symfony/dependency-injection": "^4.4|^5.0" + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.0-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2963,55 +3600,75 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A library to manipulate MIME messages", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ - "mime", - "mime-type" + "compatibility", + "ctype", + "polyfill", + "portable" ], - "time": "2020-03-27T16:56:45+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.13.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, - "dist": { + "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-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { - "ext-ctype": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.13-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3019,58 +3676,79 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", + "grapheme", + "intl", "polyfill", - "portable" + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.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": "2019-11-27T13:56:44+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.15-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - }, "files": [ "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3079,59 +3757,80 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "idn", "intl", + "normalizer", "polyfill", "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.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-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" }, "suggest": { "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.15-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3156,38 +3855,56 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.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-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "10112722600777e02d2745716b70c5db4ca70442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", + "reference": "10112722600777e02d2745716b70c5db4ca70442", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.15-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3211,38 +3928,56 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.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-06-19T12:30:46+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.13.1", + "name": "symfony/polyfill-php80", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", - "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.13-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3252,6 +3987,10 @@ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -3261,7 +4000,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3269,58 +4008,58 @@ "portable", "shim" ], - "time": "2019-11-27T16:25:15+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.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-05-31T15:07:36+00:00" }, { - "name": "symfony/routing", - "version": "v5.0.3", + "name": "symfony/polyfill-php83", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "7da33371d8ecfed6c9d93d87c73749661606f803" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7da33371d8ecfed6c9d93d87c73749661606f803", - "reference": "7da33371d8ecfed6c9d93d87c73749661606f803", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", "shasum": "" }, "require": { - "php": "^7.2.5" - }, - "conflict": { - "symfony/config": "<5.0", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "psr/log": "~1.0", - "symfony/config": "^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation loader", - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" + "php": ">=7.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.0-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Routing\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3329,55 +4068,80 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Routing Component", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "router", - "routing", - "uri", - "url" + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2020-01-04T14:08:26+00:00" + "time": "2024-06-19T12:35:24+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.0.1", + "name": "symfony/routing", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "144c5e51266b281231e947b51223ba14acf1a749" + "url": "https://github.com/symfony/routing.git", + "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", - "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "url": "https://api.github.com/repos/symfony/routing/zipball/8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", + "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/container": "^1.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, - "suggest": { - "symfony/service-implementation": "" + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } + "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\\Contracts\\Service\\": "" - } + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3385,56 +4149,80 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2019-11-18T17:27:11+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { - "name": "symfony/translation-contracts", - "version": "v2.0.1", + "name": "symfony/service-contracts", + "version": "v3.5.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", - "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, - "suggest": { - "symfony/translation-implementation": "" + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - } + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3450,7 +4238,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to translation", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ "abstractions", @@ -3460,86 +4248,64 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.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-04-18T09:32:20+00:00" }, { - "name": "symfony/twig-bridge", - "version": "v5.0.3", + "name": "symfony/string", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/twig-bridge.git", - "reference": "39cc296147e010af3c13d7734a21528426bd46ff" + "url": "https://github.com/symfony/string.git", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/39cc296147e010af3c13d7734a21528426bd46ff", - "reference": "39cc296147e010af3c13d7734a21528426bd46ff", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.10|^3.0" + "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/console": "<4.4", - "symfony/form": "<5.0", - "symfony/http-foundation": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.0", - "symfony/workflow": "<4.4" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "egulias/email-validator": "^2.1.10", - "symfony/asset": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/form": "^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/routing": "^4.4|^5.0", - "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-http": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^5.0", - "symfony/web-link": "^4.4|^5.0", - "symfony/workflow": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0", - "twig/cssinliner-extra": "^2.12", - "twig/inky-extra": "^2.12", - "twig/markdown-extra": "^2.12" - }, - "suggest": { - "symfony/asset": "For using the AssetExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/finder": "", - "symfony/form": "For using the FormExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/web-link": "For using the WebLinkExtension", - "symfony/yaml": "For using the YamlExtension" - }, - "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } + "symfony/emoji": "^7.1", + "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\\Bridge\\Twig\\": "" + "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3551,67 +4317,271 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Twig Bridge", + "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", - "time": "2020-01-08T17:33:29+00:00" - }, - { + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-22T10:25:37+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-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.5.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-04-18T09:32:20+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "96e6e12a63db80bcedefc012042d2cb2d1a015f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/96e6e12a63db80bcedefc012042d2cb2d1a015f8", + "reference": "96e6e12a63db80bcedefc012042d2cb2d1a015f8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.9" + }, + "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/emoji": "^7.1", + "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.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { "name": "symfony/twig-bundle", - "version": "v5.0.3", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "1bd6192a7742d7807b9ecd0eff347ea549a19390" + "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/1bd6192a7742d7807b9ecd0eff347ea549a19390", - "reference": "1bd6192a7742d7807b9ecd0eff347ea549a19390", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d48c2f08c2f315e749f0e18fc4945b7be8afe1e5", + "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/config": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/twig-bridge": "^5.0", - "twig/twig": "^2.10|^3.0" + "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/dependency-injection": "<4.4", - "symfony/framework-bundle": "<5.0", - "symfony/translation": "<5.0" + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" }, "require-dev": { - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0", - "symfony/asset": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/form": "^4.4|^5.0", - "symfony/framework-bundle": "^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^5.0", - "symfony/web-link": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" @@ -3634,82 +4604,87 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony TwigBundle", + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", - "time": "2020-01-04T14:08:26+00:00" + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/validator", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "bad9814bf042b01b326c77cbb7740a03e5667ccf" + "reference": "ba711a6cfc008544dad059abb3c1d997f1472237" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/bad9814bf042b01b326c77cbb7740a03e5667ccf", - "reference": "bad9814bf042b01b326c77cbb7740a03e5667ccf", + "url": "https://api.github.com/repos/symfony/validator/zipball/ba711a6cfc008544dad059abb3c1d997f1472237", + "reference": "ba711a6cfc008544dad059abb3c1d997f1472237", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^1.1|^2" + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" }, "conflict": { - "doctrine/lexer": "<1.0.2", - "phpunit/phpunit": "<5.4.3", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.4", - "symfony/translation": "<4.4", - "symfony/yaml": "<4.4" + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<7.0", + "symfony/expression-language": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/intl": "<6.4", + "symfony/property-info": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0", - "egulias/email-validator": "^2.1.10", - "symfony/cache": "^4.4|^5.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/intl": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/property-info": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader.", - "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the mapping cache.", - "symfony/config": "", - "symfony/expression-language": "For using the Expression validator", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For accessing properties within comparison constraints", - "symfony/property-info": "To automatically add NotNull and Type constraints", - "symfony/translation": "For translating validation errors.", - "symfony/yaml": "" + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^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/http-client": "^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/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/type-info": "^7.1", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Validator\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3726,52 +4701,60 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Validator Component", + "description": "Provides tools to validate values", "homepage": "https://symfony.com", - "time": "2020-01-21T08:40:24+00:00" + "support": { + "source": "https://github.com/symfony/validator/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.0.3", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "ccb1be566ae15f790020f917f06d1da0b04fe47b" + "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ccb1be566ae15f790020f917f06d1da0b04fe47b", - "reference": "ccb1be566ae15f790020f917f06d1da0b04fe47b", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/86af4617cca75a6e28598f49ae0690f3b9d4591f", + "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" + "symfony/console": "<6.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "twig/twig": "^2.4|^3.0" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "files": [ "Resources/functions/dump.php" @@ -3797,40 +4780,54 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony mechanism for exploring and dumping PHP variables", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", "homepage": "https://symfony.com", "keywords": [ "debug", "dump" ], - "time": "2020-01-04T14:08:26+00:00" + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.0.3", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "960f9ac0fdbd642461ed29d7717aeb2a94d428b9" + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/960f9ac0fdbd642461ed29d7717aeb2a94d428b9", - "reference": "960f9ac0fdbd642461ed29d7717aeb2a94d428b9", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=8.2" }, "require-dev": { - "symfony/var-dumper": "^4.4|^5.0" + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\VarExporter\\": "" @@ -3853,7 +4850,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", "homepage": "https://symfony.com", "keywords": [ "clone", @@ -3861,43 +4858,57 @@ "export", "hydrate", "instantiate", + "lazy-loading", + "proxy", "serialize" ], - "time": "2020-01-04T14:08:26+00:00" + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.1.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": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/yaml", - "version": "v5.0.3", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "69b44e3b8f90949aee2eb3aa9b86ceeb01cbf62a" + "reference": "fa34c77015aa6720469db7003567b9f772492bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/69b44e3b8f90949aee2eb3aa9b86ceeb01cbf62a", - "reference": "69b44e3b8f90949aee2eb3aa9b86ceeb01cbf62a", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<4.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/console": "^6.4|^7.0" }, + "bin": [ + "Resources/bin/yaml-lint" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" @@ -3920,40 +4931,60 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", - "time": "2020-01-21T11:12:28+00:00" + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { "name": "twig/twig", - "version": "v3.0.1", + "version": "v3.10.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "28f856a4c57eeb24485916e8a68403f41a133616" + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/28f856a4c57eeb24485916e8a68403f41a133616", - "reference": "28f856a4c57eeb24485916e8a68403f41a133616", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.0" + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -3984,34 +5015,49 @@ "keywords": [ "templating" ], - "time": "2019-12-28T07:17:28+00:00" + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.10.3" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-05-16T10:04:27+00:00" }, { "name": "webimpress/safe-writer", - "version": "2.0.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/webimpress/safe-writer.git", - "reference": "d03bea3b98abe1d4c8b24cbebf524361ffaafee4" + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/d03bea3b98abe1d4c8b24cbebf524361ffaafee4", - "reference": "d03bea3b98abe1d4c8b24cbebf524361ffaafee4", + "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", "shasum": "" }, "require": { - "php": "^7.2" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^8.4.3", - "webimpress/coding-standard": "dev-develop" + "phpunit/phpunit": "^9.5.4", + "vimeo/psalm": "^4.7", + "webimpress/coding-standard": "^1.2.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev", - "dev-develop": "2.1.x-dev", + "dev-master": "2.2.x-dev", + "dev-develop": "2.3.x-dev", "dev-release-1.0": "1.0.x-dev" } }, @@ -4032,119 +5078,288 @@ "safe writer", "webimpress" ], - "time": "2019-11-27T19:40:53+00:00" + "support": { + "issues": "https://github.com/webimpress/safe-writer/issues", + "source": "https://github.com/webimpress/safe-writer/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2021-04-19T16:34:45+00:00" } ], "packages-dev": [ { - "name": "behat/behat", - "version": "dev-master", + "name": "amphp/amp", + "version": "v2.6.4", "source": { "type": "git", - "url": "https://github.com/Behat/Behat.git", - "reference": "7aa8161be7499ecc8b73ccc6dcd18f4062e6bf8e" + "url": "https://github.com/amphp/amp.git", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Behat/zipball/7aa8161be7499ecc8b73ccc6dcd18f4062e6bf8e", - "reference": "7aa8161be7499ecc8b73ccc6dcd18f4062e6bf8e", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { - "behat/gherkin": "^4.6.0", - "behat/transliterator": "^1.2", - "container-interop/container-interop": "^1.2", - "ext-mbstring": "*", - "php": ">=5.3.3", - "psr/container": "^1.0", - "symfony/config": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", - "symfony/console": "^2.7.51 || ^2.8.33 || ^3.3.15 || ^3.4.3 || ^4.0.3 || ^5.0", - "symfony/dependency-injection": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", - "symfony/event-dispatcher": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", - "symfony/translation": "^2.7.51 || ^3.0 || ^4.0 || ^5.0", - "symfony/yaml": "^2.7.51 || ^3.0 || ^4.0 || ^5.0" + "php": ">=7.1" }, "require-dev": { - "herrera-io/box": "~1.6.1", - "phpunit/phpunit": "^4.8.36 || ^6.3", - "symfony/process": "~2.5 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "ext-dom": "Needed to output test results in JUnit format." + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, - "bin": [ - "bin/behat" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.5.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], "psr-4": { - "Behat\\Behat\\": "src/Behat/Behat/", - "Behat\\Testwork\\": "src/Behat/Testwork/" - } - }, - "autoload-dev": { - "psr-4": { - "Behat\\Tests\\": "tests/" + "Amp\\": "lib" } }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.4" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T18:52:26+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-13T18:00:56+00:00" + }, + { + "name": "behat/behat", + "version": "v3.14.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "2a3832d9cb853a794af3a576f9e524ae460f3340" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/2a3832d9cb853a794af3a576f9e524ae460f3340", + "reference": "2a3832d9cb853a794af3a576f9e524ae460f3340", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.9.0", + "behat/transliterator": "^1.2", + "ext-mbstring": "*", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/translation": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "herrera-io/box": "~1.6.1", + "phpspec/prophecy": "^1.15", + "phpunit/phpunit": "^8.5 || ^9.0", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "vimeo/psalm": "^4.8" + }, + "suggest": { + "ext-dom": "Needed to output test results in JUnit format." + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Hook\\": "src/Behat/Hook/", + "Behat\\Step\\": "src/Behat/Step/", + "Behat\\Behat\\": "src/Behat/Behat/", + "Behat\\Testwork\\": "src/Behat/Testwork/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } ], - "description": "Scenario-oriented BDD framework for PHP 5.3", + "description": "Scenario-oriented BDD framework for PHP", "homepage": "http://behat.org/", "keywords": [ "Agile", "BDD", - "Examples", "ScenarioBDD", "Scrum", "StoryBDD", - "Symfony", "User story", "business", "development", "documentation", + "examples", + "symfony", "testing" ], "support": { - "source": "https://github.com/Behat/Behat/tree/master", - "issues": "https://github.com/Behat/Behat/issues" + "issues": "https://github.com/Behat/Behat/issues", + "source": "https://github.com/Behat/Behat/tree/v3.14.0" }, - "time": "2020-01-14T16:41:31+00:00" + "time": "2023-12-09T13:55:02+00:00" }, { "name": "behat/gherkin", - "version": "v4.6.0", + "version": "v4.9.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07" + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07", - "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", "shasum": "" }, "require": { - "php": ">=5.3.1" + "php": "~7.2|~8.0" }, "require-dev": { - "phpunit/phpunit": "~4.5|~5", - "symfony/phpunit-bridge": "~2.7|~3|~4", - "symfony/yaml": "~2.3|~3|~4" + "cucumber/cucumber": "dev-gherkin-22.0.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -4152,7 +5367,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "4.x-dev" } }, "autoload": { @@ -4171,7 +5386,7 @@ "homepage": "http://everzet.com" } ], - "description": "Gherkin DSL parser for PHP 5.3", + "description": "Gherkin DSL parser for PHP", "homepage": "http://behat.org/", "keywords": [ "BDD", @@ -4181,39 +5396,47 @@ "gherkin", "parser" ], - "time": "2019-01-16T14:22:17+00:00" + "support": { + "issues": "https://github.com/Behat/Gherkin/issues", + "source": "https://github.com/Behat/Gherkin/tree/v4.9.0" + }, + "time": "2021-10-12T13:05:09+00:00" }, { "name": "behat/mink", - "version": "v1.7.1", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/minkphp/Mink.git", - "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9" + "reference": "d8527fdf8785aad38455fb426af457ab9937aece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/Mink/zipball/e6930b9c74693dff7f4e58577e1b1743399f3ff9", - "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/d8527fdf8785aad38455fb426af457ab9937aece", + "reference": "d8527fdf8785aad38455fb426af457ab9937aece", "shasum": "" }, "require": { - "php": ">=5.3.1", - "symfony/css-selector": "~2.1|~3.0" + "php": ">=7.2", + "symfony/css-selector": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0" + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^8.5.22 || ^9.5.11", + "symfony/error-handler": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" }, "suggest": { - "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", - "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-browserkit-driver": "fast headless driver for any app without JS emulation", "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", - "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)", + "dmore/chrome-mink-driver": "fast and JS-enabled driver for any app (requires chromium or google chrome)" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -4233,42 +5456,54 @@ } ], "description": "Browser controller/emulator abstraction for PHP", - "homepage": "http://mink.behat.org/", + "homepage": "https://mink.behat.org/", "keywords": [ "browser", "testing", "web" ], - "time": "2016-03-05T08:26:18+00:00" + "support": { + "issues": "https://github.com/minkphp/Mink/issues", + "source": "https://github.com/minkphp/Mink/tree/v1.11.0" + }, + "time": "2023-12-09T11:23:23+00:00" }, { "name": "behat/mink-browserkit-driver", - "version": "dev-symfony-5", + "version": "v2.2.0", "source": { "type": "git", - "url": "https://github.com/ruudk/MinkBrowserKitDriver.git", - "reference": "3f4a066ad6ab001fd4f838aa8ec27b31e8afbadf" + "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", + "reference": "16d53476e42827ed3aafbfa4fde17a1743eafd50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ruudk/MinkBrowserKitDriver/zipball/3f4a066ad6ab001fd4f838aa8ec27b31e8afbadf", - "reference": "3f4a066ad6ab001fd4f838aa8ec27b31e8afbadf", + "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/16d53476e42827ed3aafbfa4fde17a1743eafd50", + "reference": "16d53476e42827ed3aafbfa4fde17a1743eafd50", "shasum": "" }, "require": { - "behat/mink": "^1.7.1@dev", - "php": ">=5.3.6", - "symfony/browser-kit": "~2.3|~3.0|~4.0|~5.0", - "symfony/dom-crawler": "~2.3|~3.0|~4.0|~5.0" + "behat/mink": "^1.11.0@dev", + "ext-dom": "*", + "php": ">=7.2", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/dom-crawler": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { "mink/driver-testsuite": "dev-master", - "symfony/http-kernel": "~2.3|~3.0|~4.0|~5.0" + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/error-handler": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/http-client": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/mime": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "yoast/phpunit-polyfills": "^1.0" }, "type": "mink-driver", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -4276,11 +5511,7 @@ "Behat\\Mink\\Driver\\": "src/" } }, - "autoload-dev": { - "psr-4": { - "Behat\\Mink\\Tests\\Driver\\": "tests" - } - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -4292,7 +5523,7 @@ } ], "description": "Symfony2 BrowserKit driver for Mink framework", - "homepage": "http://mink.behat.org/", + "homepage": "https://mink.behat.org/", "keywords": [ "Mink", "Symfony2", @@ -4300,97 +5531,37 @@ "testing" ], "support": { - "source": "https://github.com/ruudk/MinkBrowserKitDriver/tree/symfony-5" - }, - "time": "2019-12-23T19:35:20+00:00" - }, - { - "name": "behat/mink-extension", - "version": "dev-patch-4", - "source": { - "type": "git", - "url": "https://github.com/DonCallisto/MinkExtension.git", - "reference": "9cc94afdfe2e1df3058e5ef70970dded1504673b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/DonCallisto/MinkExtension/zipball/9cc94afdfe2e1df3058e5ef70970dded1504673b", - "reference": "9cc94afdfe2e1df3058e5ef70970dded1504673b", - "shasum": "" - }, - "require": { - "behat/behat": "^3.0.5", - "behat/mink": "^1.5", - "php": ">=5.3.2", - "symfony/config": "^2.7|^3.0|^4.0|^5.0" - }, - "require-dev": { - "behat/mink-goutte-driver": "^1.1", - "phpspec/phpspec": "^2.0" - }, - "type": "behat-extension", - "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev" - } - }, - "autoload": { - "psr-0": { - "Behat\\MinkExtension": "src/" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com" - }, - { - "name": "Christophe Coevoet", - "email": "stof@notk.org" - } - ], - "description": "Mink extension for Behat", - "homepage": "http://extensions.behat.org/mink", - "keywords": [ - "browser", - "gui", - "test", - "web" - ], - "support": { - "source": "https://github.com/DonCallisto/MinkExtension/tree/patch-4" + "issues": "https://github.com/minkphp/MinkBrowserKitDriver/issues", + "source": "https://github.com/minkphp/MinkBrowserKitDriver/tree/v2.2.0" }, - "time": "2019-11-24T19:02:47+00:00" + "time": "2023-12-09T11:30:50+00:00" }, { "name": "behat/transliterator", - "version": "v1.3.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/Behat/Transliterator.git", - "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc" + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Transliterator/zipball/3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc", - "reference": "3c4ec1d77c3d05caa1f0bf8fb3aae4845005c7fc", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, "require-dev": { "chuyskywalker/rolling-curl": "^3.1", "php-yaoi/php-yaoi": "^1.0", - "phpunit/phpunit": "^4.8.36|^6.3" + "phpunit/phpunit": "^8.5.25 || ^9.5.19" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -4408,91 +5579,98 @@ "slug", "transliterator" ], - "time": "2020-01-14T16:39:13+00:00" + "support": { + "issues": "https://github.com/Behat/Transliterator/issues", + "source": "https://github.com/Behat/Transliterator/tree/v1.5.0" + }, + "time": "2022-03-30T09:27:43+00:00" }, { - "name": "container-interop/container-interop", - "version": "1.2.0", + "name": "codelytv/coding-style", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/container-interop/container-interop.git", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + "url": "https://github.com/CodelyTV/php-coding_style-codely.git", + "reference": "41d7e6b651619467b05018666606a1ef0958263e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "url": "https://api.github.com/repos/CodelyTV/php-coding_style-codely/zipball/41d7e6b651619467b05018666606a1ef0958263e", + "reference": "41d7e6b651619467b05018666606a1ef0958263e", "shasum": "" }, "require": { - "psr/container": "^1.0" + "symplify/easy-coding-standard": "^12.0" }, "type": "library", "autoload": { "psr-4": { - "Interop\\Container\\": "src/Interop/Container/" + "CodelyTv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "AGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Codely", + "homepage": "https://codely.com" + } + ], + "description": "PHP Coding Style rules we use in Codely", + "keywords": [ + "Code style", + "static analysis" + ], + "support": { + "issues": "https://github.com/CodelyTV/php-coding_style-codely/issues", + "source": "https://github.com/CodelyTV/php-coding_style-codely/tree/1.3.0" + }, + "funding": [ + { + "url": "https://bit.ly/CodelyTvPro", + "type": "custom" + } ], - "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", - "homepage": "https://github.com/container-interop/container-interop", - "abandoned": "psr/container", - "time": "2017-02-14T19:40:03+00:00" + "time": "2024-08-05T14:17:14+00:00" }, { - "name": "friends-of-behat/symfony-extension", - "version": "v2.1.0-BETA.1", + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", "source": { "type": "git", - "url": "https://github.com/FriendsOfBehat/SymfonyExtension.git", - "reference": "c2afff0d15e4ad5fb2cc085baef17a051b9c3ad9" + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfBehat/SymfonyExtension/zipball/c2afff0d15e4ad5fb2cc085baef17a051b9c3ad9", - "reference": "c2afff0d15e4ad5fb2cc085baef17a051b9c3ad9", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", "shasum": "" }, "require": { - "behat/behat": "^3.4", - "php": "^7.1", - "symfony/dependency-injection": "^3.4|^4.4|^5.0", - "symfony/http-kernel": "^3.4|^4.4|^5.0", - "symfony/proxy-manager-bridge": "^3.4|^4.4|^5.0" + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" }, - "conflict": { - "symplify/package-builder": "^7.2", - "symplify/smart-file-system": "^7.2" + "replace": { + "ocramius/package-versions": "1.11.99" }, "require-dev": { - "behat/mink-selenium2-driver": "^1.3", - "friends-of-behat/mink": "^1.7", - "friends-of-behat/mink-browserkit-driver": "^1.3", - "friends-of-behat/mink-extension": "^2.2", - "friends-of-behat/page-object-extension": "^0.3.1", - "friends-of-behat/service-container-extension": "^1.0", - "phpstan/phpstan-shim": "^0.11", - "sylius-labs/coding-standard": "^3.0", - "symfony/browser-kit": "^3.4|^4.4|^5.0", - "symfony/framework-bundle": "^3.4|^4.4|^5.0", - "symfony/process": "^3.4|^4.4|^5.0", - "symfony/yaml": "^3.4|^4.4|^5.0" - }, - "suggest": { - "behat/mink-browserkit-driver": "^1.3" + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" }, - "type": "symfony-bundle", + "type": "composer-plugin", "extra": { + "class": "PackageVersions\\Installer", "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "FriendsOfBehat\\SymfonyExtension\\": "src/" + "PackageVersions\\": "src/PackageVersions" } }, "notification-url": "https://packagist.org/downloads/", @@ -4501,45 +5679,74 @@ ], "authors": [ { - "name": "Kamil Kokot", - "email": "kamil@kokot.me", - "homepage": "https://kamilkokot.com" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" } ], - "description": "Integrates Behat with Symfony.", - "time": "2020-01-15T18:02:23+00:00" + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "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-01-17T14:14:24+00:00" }, { - "name": "fzaninotto/faker", - "version": "v1.9.1", + "name": "composer/pcre", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" + "url": "https://github.com/composer/pcre.git", + "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", - "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", + "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.8" }, "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^2.9.2" + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4548,96 +5755,927 @@ ], "authors": [ { - "name": "FranΓ§ois Zaninotto" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "data", - "faker", - "fixtures" + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.2.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": "2019-12-12T13:22:17+00:00" + "time": "2024-07-25T09:36:02+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v2.0.0", + "name": "composer/semver", + "version": "3.4.2", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + "url": "https://github.com/composer/semver.git", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { - "php": "^5.3|^7.0" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "^1.0" + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "hamcrest" - ] + "psr-4": { + "Composer\\Semver\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD" - ], - "description": "This is the PHP port of Hamcrest Matchers", - "keywords": [ - "test" + "MIT" ], - "time": "2016-01-20T08:20:44+00:00" - }, - { + "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.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-07-12T11:35:52+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "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", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "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": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "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": "2024-05-06T16:37:16+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "FranΓ§ois Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + }, + "time": "2024-01-02T13:46:09+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "ThΓ©o FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "friends-of-behat/mink-extension", + "version": "v2.7.5", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfBehat/MinkExtension.git", + "reference": "854336030e11983f580f49faad1b49a1238f9846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfBehat/MinkExtension/zipball/854336030e11983f580f49faad1b49a1238f9846", + "reference": "854336030e11983f580f49faad1b49a1238f9846", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.5", + "behat/mink": "^1.5", + "php": ">=7.4", + "symfony/config": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "replace": { + "behat/mink-extension": "self.version" + }, + "require-dev": { + "behat/mink-goutte-driver": "^1.1 || ^2.0", + "phpspec/phpspec": "^6.0 || ^7.0 || 7.1.x-dev" + }, + "type": "behat-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\MinkExtension": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com" + }, + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Mink extension for Behat", + "homepage": "http://extensions.behat.org/mink", + "keywords": [ + "browser", + "gui", + "test", + "web" + ], + "support": { + "issues": "https://github.com/FriendsOfBehat/MinkExtension/issues", + "source": "https://github.com/FriendsOfBehat/MinkExtension/tree/v2.7.5" + }, + "time": "2024-01-11T09:12:02+00:00" + }, + { + "name": "friends-of-behat/symfony-extension", + "version": "v2.6.0", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfBehat/SymfonyExtension.git", + "reference": "dfb1c9c96cc0fb7c8e1caa060695426a12e1efbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfBehat/SymfonyExtension/zipball/dfb1c9c96cc0fb7c8e1caa060695426a12e1efbd", + "reference": "dfb1c9c96cc0fb7c8e1caa060695426a12e1efbd", + "shasum": "" + }, + "require": { + "behat/behat": "^3.6.1", + "php": "^8.1", + "symfony/dependency-injection": "^6.2 || ^7.0", + "symfony/http-kernel": "^6.2 || ^7.0" + }, + "require-dev": { + "behat/mink": "^1.9", + "behat/mink-browserkit-driver": "^2.0", + "behat/mink-selenium2-driver": "^1.3", + "friends-of-behat/mink-extension": "^2.5", + "friends-of-behat/page-object-extension": "^0.3.2", + "friends-of-behat/service-container-extension": "^1.1", + "sylius-labs/coding-standard": ">=4.1.1, <=4.2.1", + "symfony/browser-kit": "^6.2 || ^7.0", + "symfony/framework-bundle": "^6.2 || ^7.0", + "symfony/process": "^6.2 || ^7.0", + "symfony/yaml": "^6.2 || ^7.0", + "vimeo/psalm": "4.30.0" + }, + "suggest": { + "behat/mink": "^1.9", + "behat/mink-browserkit-driver": "^2.0", + "friends-of-behat/mink-extension": "^2.5" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-4": { + "FriendsOfBehat\\SymfonyExtension\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kamil Kokot", + "email": "kamil@kokot.me", + "homepage": "https://kamilkokot.com" + } + ], + "description": "Integrates Behat with Symfony.", + "support": { + "issues": "https://github.com/FriendsOfBehat/SymfonyExtension/issues", + "source": "https://github.com/FriendsOfBehat/SymfonyExtension/tree/v2.6.0" + }, + "time": "2024-07-03T15:49:43+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, + { "name": "mockery/mockery", - "version": "1.3.1", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "PΓ‘draic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "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", + "phpspec/prophecy": "^1.10", + "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.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.4.1", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be" + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "~2.0", - "lib-pcre": ">=7.0", - "php": ">=5.6.0" + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + }, + "time": "2024-01-31T06:18:54+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.19.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "4.9-dev" } }, "autoload": { - "psr-0": { - "Mockery": "library/" + "psr-4": { + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", @@ -4646,104 +6684,109 @@ ], "authors": [ { - "name": "PΓ‘draic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "name": "Nikita Popov" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework", - "homepage": "https://github.com/mockery/mockery", + "description": "A PHP parser written in PHP", "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" + "parser", + "php" ], - "time": "2019-12-26T09:49:15+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + }, + "time": "2024-03-17T08:10:35+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.9.5", + "name": "pdepend/pdepend", + "version": "2.16.2", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", - "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", "shasum": "" }, "require": { - "php": "^7.1" - }, - "replace": { - "myclabs/deep-copy": "self.version" + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" }, + "bin": [ + "src/bin/pdepend" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, - "files": [ - "src/DeepCopy/deep_copy.php" - ] + "PDepend\\": "src/main/php/PDepend" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "Create deep copies (clones) of your objects", + "description": "Official version of pdepend to be handled with Composer", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" ], - "time": "2020-01-17T21:11:47+00:00" + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -4773,24 +6816,34 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "2.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -4820,32 +6873,90 @@ } ], "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" + "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": "phpdocumentor/reflection-common", - "version": "2.0.0", + "name": "phpat/phpat", + "version": "0.10.18", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "url": "https://github.com/carlosas/phpat.git", + "reference": "4c29e330fb306876bca3174aa4b097d0d8611964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/carlosas/phpat/zipball/4c29e330fb306876bca3174aa4b097d0d8611964", + "reference": "4c29e330fb306876bca3174aa4b097d0d8611964", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^1.3" }, "require-dev": { - "phpunit/phpunit": "~6" + "friendsofphp/php-cs-fixer": "3.46", + "kubawerlos/php-cs-fixer-custom-fixers": "3.18", + "phpunit/phpunit": "^9.0 || ^10.0", + "vimeo/psalm": "^5.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "PHPat\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carlos Alandete Sastre", + "email": "carlos.alandete@gmail.com" + } + ], + "description": "PHP Architecture Tester", + "support": { + "issues": "https://github.com/carlosas/phpat/issues", + "source": "https://github.com/carlosas/phpat/tree/0.10.18" + }, + "time": "2024-07-05T14:56:19+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -4872,45 +6983,53 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.4", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", - "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", - "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", - "webmozart/assert": "^1.0" + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" }, "require-dev": { - "doctrine/instantiator": "^1.0.5", - "mockery/mockery": "^1.0", - "phpdocumentor/type-resolver": "0.4.*", - "phpunit/phpunit": "^6.4" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4921,157 +7040,305 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-12-28T18:55:12+00:00" + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + }, + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.0.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { - "php": "^7.1", - "phpdocumentor/reflection-common": "^2.0" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" }, "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc WΓΌrth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.29.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "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": { - "phpDocumentor\\Reflection\\": "src" + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" + "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.29.1" + }, + "time": "2024-05-31T08:52:43+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.10.2", + "name": "phpstan/phpstan", + "version": "1.11.9", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "e370bcddadaede0c1716338b262346f40d296f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82", + "reference": "e370bcddadaede0c1716338b262346f40d296f82", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + "php": "^7.2|^8.0" }, - "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "conflict": { + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10.x-dev" - } - }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "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": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" + "url": "https://github.com/ondrejmirtes", + "type": "github" }, { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "url": "https://github.com/phpstan", + "type": "github" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2020-01-20T15:57:02+00:00" + "time": "2024-08-01T16:25:18+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.10", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", - "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": "^7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.1", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" + "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": "^8.2.2" + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-xdebug": "^2.7.2" + "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": "7.0-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -5097,32 +7364,43 @@ "testing", "xunit" ], - "time": "2019-11-20T13:55:58+00:00" + "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.31" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.2", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -5147,26 +7425,48 @@ "filesystem", "iterator" ], - "time": "2018-09-13T20:33:42+00:00" + "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-text-template", - "version": "1.2.1", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": ">=5.3.3" + "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/" @@ -5183,37 +7483,47 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "template" + "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": "2015-06-21T13:50:34+00:00" + "time": "2020-09-28T05:58:55+00:00" }, { - "name": "phpunit/php-timer", - "version": "2.1.2", + "name": "phpunit/php-text-template", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5232,38 +7542,47 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "timer" + "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": "2019-06-07T04:22:29+00:00" + "time": "2020-10-26T05:33:50+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "3.1.1", + "name": "phpunit/php-timer", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5278,64 +7597,73 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "tokenizer" + "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": "2019-09-17T06:23:10+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { "name": "phpunit/phpunit", - "version": "8.5.2", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/018b6ac3c8ab20916db85fa91bf6465acb64d1e0", - "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2.0", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.9.1", - "phar-io/manifest": "^1.0.3", - "phar-io/version": "^2.0.1", - "php": "^7.2", - "phpspec/prophecy": "^1.8.1", - "phpunit/php-code-coverage": "^7.0.7", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.2", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.2", - "sebastian/exporter": "^3.1.1", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", - "sebastian/version": "^2.0.1" - }, - "require-dev": { - "ext-pdo": "*" + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" + "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" @@ -5343,33 +7671,293 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "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.20" + }, + "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-07-10T11:45:39+00:00" + }, + { + "name": "psalm/plugin-mockery", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-mockery.git", + "reference": "876247d15f91df08240d00dac69c5135b6689283" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-mockery/zipball/876247d15f91df08240d00dac69c5135b6689283", + "reference": "876247d15f91df08240d00dac69c5135b6689283", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "mockery/mockery": "^1.0", + "php": "~7.4 || ~8.0 || ~8.1 || ~8.2", + "vimeo/psalm": "dev-master || ^5.0@rc || ^5.0" + }, + "require-dev": { + "codeception/codeception": "^4.1.9", + "phpunit/phpunit": "^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.13.1" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\MockeryPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\MockeryPlugin\\": [ + "." + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for Mockery", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-mockery/issues", + "source": "https://github.com/psalm/psalm-plugin-mockery/tree/1.1.0" + }, + "time": "2022-11-25T07:16:18+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.18.4", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "e4ab3096653d9eb6f6d0ea5f4461898d59ae4dbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/e4ab3096653d9eb6f6d0ea5f4461898d59ae4dbc", + "reference": "e4ab3096653d9eb6f6d0ea5f4461898d59ae4dbc", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "vimeo/psalm": "dev-master || dev-4.x || ^4.7.1 || ^5@beta || ^5.0" + }, + "conflict": { + "phpunit/phpunit": "<7.5" + }, + "require-dev": { + "codeception/codeception": "^4.0.3", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.11.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.18.4" + }, + "time": "2022-12-03T07:47:07+00:00" + }, + { + "name": "psalm/plugin-symfony", + "version": "v5.2.5", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-symfony.git", + "reference": "fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196", + "reference": "fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^8.1", + "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", + "vimeo/psalm": "^5.16" + }, + "require-dev": { + "doctrine/annotations": "^1.8|^2", + "doctrine/orm": "^2.9", + "phpunit/phpunit": "~7.5 || ~9.5", + "symfony/cache-contracts": "^1.0 || ^2.0", + "symfony/console": "*", + "symfony/form": "^5.0 || ^6.0 || ^7.0", + "symfony/messenger": "^5.0 || ^6.0 || ^7.0", + "symfony/security-core": "*", + "symfony/serializer": "^5.0 || ^6.0 || ^7.0", + "symfony/validator": "*", + "twig/twig": "^2.10 || ^3.0", + "weirdan/codeception-psalm-module": "dev-master" + }, + "suggest": { + "weirdan/doctrine-psalm-plugin": "If Doctrine is used, it is recommended install this plugin" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\SymfonyPsalmPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\SymfonyPsalmPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Farhad Safarov", + "email": "farhad.safarov@gmail.com" + } + ], + "description": "Psalm Plugin for Symfony", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-symfony/issues", + "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.2.5" + }, + "time": "2024-07-03T11:57:02+00:00" + }, + { + "name": "rector/rector", + "version": "0.18.13", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/f8011a76d36aa4f839f60f3b4f97707d97176618", + "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.10.35" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "authors": [ + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.18.13" + }, + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "url": "https://github.com/tomasvotruba", + "type": "github" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2020-01-08T08:49:49+00:00" + "time": "2023-12-20T16:08:01+00:00" }, { "name": "roave/security-advisories", @@ -5377,205 +7965,776 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "666ba252853924887ac57dc9f66e6b6af78d5a76" + "reference": "ff7456939acba6dd515a8a10aad66be6bc1b8dc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/666ba252853924887ac57dc9f66e6b6af78d5a76", - "reference": "666ba252853924887ac57dc9f66e6b6af78d5a76", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ff7456939acba6dd515a8a10aad66be6bc1b8dc1", + "reference": "ff7456939acba6dd515a8a10aad66be6bc1b8dc1", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "adodb/adodb-php": "<5.20.12", + "admidio/admidio": "<4.3.10", + "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "aheinze/cockpit": "<2.2", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.04.6", + "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", + "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9", + "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", + "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "airesvsg/acf-to-rest-api": "<=3.1", + "akaunting/akaunting": "<2.1.13", + "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", + "alextselegidis/easyappointments": "<1.5", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amazing/media2click": ">=1,<1.3.3", "amphp/artax": "<1.0.6|>=2,<2.0.6", - "amphp/http": "<1.0.1", - "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", - "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", - "aws/aws-sdk-php": ">=3,<3.2.1", + "amphp/http": "<=1.7.2|>=2,<=2.1", + "amphp/http-client": ">=4,<4.4", + "anchorcms/anchor-cms": "<=0.12.7", + "andreapollastri/cipi": "<=3.1.15", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "apache-solr-for-typo3/solr": "<2.8.3", + "apereo/phpcas": "<1.6", + "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", + "appwrite/server-ce": "<=1.2.1", + "arc/web": "<3", + "area17/twill": "<1.2.5|>=2,<2.5.3", + "artesaos/seotools": "<0.17.2", + "asymmetricrypt/asymmetricrypt": "<9.9.99", + "athlon1600/php-proxy": "<=5.1", + "athlon1600/php-proxy-app": "<=3", + "austintoddj/canvas": "<=3.4.2", + "auth0/wordpress": "<=4.6", + "automad/automad": "<=2.0.0.0-alpha5", + "automattic/jetpack": "<9.8", + "awesome-support/awesome-support": "<=6.0.7", + "aws/aws-sdk-php": "<3.288.1", + "azuracast/azuracast": "<0.18.3", + "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", + "backpack/crud": "<3.4.9", + "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", + "badaso/core": "<2.7", + "bagisto/bagisto": "<2.1", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "barryvdh/laravel-translation-manager": "<0.6.2", + "barzahlen/barzahlen-php": "<2.0.1", + "baserproject/basercms": "<5.0.9", + "bassjobsen/bootstrap-3-typeahead": ">4.0.2", + "bbpress/bbpress": "<2.6.5", + "bcosca/fatfree": "<3.7.2", + "bedita/bedita": "<4", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billz/raspap-webgui": "<=3.1.4", + "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", + "blueimp/jquery-file-upload": "==6.4.4", + "bmarshall511/wordpress_zero_spam": "<5.2.13", + "bolt/bolt": "<3.7.2", + "bolt/core": "<=4.2", + "born05/craft-twofactorauthentication": "<3.3.4", + "bottelet/flarepoint": "<2.2.1", + "bref/bref": "<2.1.17", "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", + "buddypress/buddypress": "<7.2.1", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", - "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7", + "bytefury/crater": "<6.0.2", + "cachethq/cachet": "<2.5.1", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cardgate/magento2": "<2.0.33", + "cardgate/woocommerce": "<=3.1.15", "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", - "codeigniter/framework": "<=3.0.6", - "composer/composer": "<=1-alpha.11", + "catfan/medoo": "<1.7.5", + "causal/oidc": "<2.1", + "cecil/cecil": "<7.47.1", + "centreon/centreon": "<22.10.15", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "ckeditor/ckeditor": "<4.24", + "cockpit-hq/cockpit": "<2.7|==2.7", + "codeception/codeception": "<3.1.3|>=4,<4.1.22", + "codeigniter/framework": "<3.1.9", + "codeigniter4/framework": "<4.4.7", + "codeigniter4/shield": "<1.0.0.0-beta8", + "codiad/codiad": "<=2.8.4", + "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", + "concrete5/concrete5": "<=9.3.2", + "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/core": ">=2,<3.5.39", - "contao/core-bundle": ">=4,<4.4.46|>=4.5,<4.8.6", - "contao/listing-bundle": ">=4,<4.4.8", + "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/core": "<3.5.39", + "contao/core-bundle": "<4.13.40|>=5,<5.3.4", + "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", + "contao/managed-edition": "<=1.5", + "corveda/phpsandbox": "<1.3.5", + "cosenary/instagram": "<=2.3", + "craftcms/cms": "<4.6.2|>=5.0.0.0-beta1,<=5.2.2", + "croogo/croogo": "<4", + "cuyz/valinor": "<0.12", + "czproject/git-php": "<4.0.3", + "dapphp/securimage": "<3.6.6", + "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", + "datatables/datatables": "<1.10.10", "david-garcia/phpwhois": "<=4.3.1", - "doctrine/annotations": ">=1,<1.2.7", + "dbrisinajumi/d2files": "<1", + "dcat/laravel-admin": "<=2.1.3.0-beta", + "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", + "desperado/xml-bundle": "<=0.1.7", + "devgroup/dotplant": "<2020.09.14-dev", + "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "doctrine/annotations": "<1.2.7", "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", - "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", - "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2", + "doctrine/common": "<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", "doctrine/doctrine-bundle": "<1.5.2", - "doctrine/doctrine-module": "<=0.7.1", - "doctrine/mongodb-odm": ">=1,<1.0.2", - "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", - "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", - "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.69|>=8,<8.7.11|>=8.8,<8.8.1", - "drupal/drupal": ">=7,<7.69|>=8,<8.7.11|>=8.8,<8.8.1", + "doctrine/doctrine-module": "<0.7.2", + "doctrine/mongodb-odm": "<1.0.2", + "doctrine/mongodb-odm-bundle": "<3.0.1", + "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<19.0.2", + "dompdf/dompdf": "<2.0.4", + "doublethreedigital/guest-entries": "<3.1.2", + "drupal/core": ">=6,<6.38|>=7,<7.96|>=8,<10.1.8|>=10.2,<10.2.2", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "duncanmcclean/guest-entries": "<3.1.2", + "dweeves/magmi": "<=0.7.24", + "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", + "ecodev/newsletter": "<=4", + "ectouch/ectouch": "<=2.7.2", + "egroupware/egroupware": "<23.1.20240624", + "elefant/cms": "<2.0.7", + "elgg/elgg": "<3.3.24|>=4,<4.0.5", + "elijaa/phpmemcacheadmin": "<=1.3", + "encore/laravel-admin": "<=1.8.19", "endroid/qr-code-bundle": "<3.4.2", + "enhavo/enhavo-app": "<=0.13.1", + "enshrined/svg-sanitize": "<0.15", "erusev/parsedown": "<1.7.2", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.4", - "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1", - "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3", - "ezsystems/repository-forms": ">=2.3,<2.3.2.1", + "ether/logs": "<3.0.4", + "evolutioncms/evolution": "<=3.2.3", + "exceedone/exment": "<4.4.3|>=5,<5.0.3", + "exceedone/laravel-admin": "<2.2.3|==3", + "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", + "ezsystems/ez-support-tools": ">=2.2,<2.2.3", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", + "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26|>=3.3,<3.3.39", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", + "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", + "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", + "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", + "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<4.1.1", - "firebase/php-jwt": "<2", + "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2022.08", + "fastly/magento2": "<1.2.26", + "feehi/cms": "<=2.1.1", + "feehi/feehicms": "<=2.1.1", + "fenom/fenom": "<=2.12.1", + "filegator/filegator": "<7.8", + "filp/whoops": "<2.1.13", + "fineuploader/php-traditional-server": "<=1.2.2", + "firebase/php-jwt": "<6", + "fisharebest/webtrees": "<=2.1.18", + "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", + "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8.5", + "flarum/flarum": "<0.1.0.0-beta8", + "flarum/framework": "<1.8.5", + "flarum/mentions": "<1.6.3", + "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", + "flarum/tags": "<=0.1.0.0-beta13", + "floriangaerber/magnesium": "<0.3.1", + "fluidtypo3/vhs": "<5.1.1", + "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/upload": "<1.2.3", + "foodcoopshop/foodcoopshop": ">=3.2,<3.6.1", "fooman/tcpdf": "<6.2.22", + "forkcms/forkcms": "<5.11.1", "fossar/tcpdf-parser": "<6.2.22", + "francoisjacquet/rosariosis": "<=11.5.1", + "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", + "friendsofsymfony/oauth2-php": "<1.3", "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", - "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "friendsofsymfony/user-bundle": ">=1,<1.3.5", + "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", + "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", + "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.3", + "froxlor/froxlor": "<2.1.9", + "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "gree/jose": "<=2.2", + "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "gaoming13/wechat-php-sdk": "<=1.10.2", + "genix/cms": "<=1.1.11", + "getformwork/formwork": "<1.13.1|==2.0.0.0-beta1", + "getgrav/grav": "<1.7.46", + "getkirby/cms": "<4.1.1", + "getkirby/kirby": "<=2.5.12", + "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", + "gilacms/gila": "<=1.15.4", + "gleez/cms": "<=1.3|==2", + "globalpayments/php-sdk": "<2", + "gogentooss/samlbase": "<1.2.7", + "google/protobuf": "<3.15", + "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", - "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", - "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", - "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", - "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", + "grumpydictator/firefly-iii": "<6.1.17", + "gugoan/economizzer": "<=0.9.0.0-beta1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "harvesthq/chosen": "<1.8.7", + "helloxz/imgurl": "<=2.31", + "hhxsv5/laravel-s": "<3.7.36", + "hillelcoren/invoice-ninja": "<5.3.35", + "himiklab/yii2-jqgrid-widget": "<1.0.8", + "hjue/justwriting": "<=1", + "hov/jobfair": "<1.0.13|>=2,<2.0.2", + "httpsoft/http-message": "<1.0.12", + "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6.0.0-beta1,<4.6.9", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", + "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", + "ibexa/post-install": "<=1.0.4", + "ibexa/solr": ">=4.5,<4.5.4", + "ibexa/user": ">=4,<4.4.3", + "icecoder/icecoder": "<=8.1", + "idno/known": "<=1.3.1", + "ilicmiljan/secure-props": ">=1.2,<1.2.2", + "illuminate/auth": "<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", + "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "imdbphp/imdbphp": "<=5.1.1", + "impresscms/impresscms": "<=1.4.5", + "impresspages/impresspages": "<=1.0.12", + "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3", + "in2code/ipandlanguageredirect": "<5.1.2", + "in2code/lux": "<17.6.1|>=18,<24.0.2", + "innologi/typo3-appointments": "<2.0.6", + "intelliants/subrion": "<4.2.2", + "inter-mediator/inter-mediator": "==5.5", + "ipl/web": "<0.10.1", + "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", - "james-heinrich/getid3": "<1.9.9", + "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<1.7.12", + "jasig/phpcas": "<1.3.3", + "jcbrand/converse.js": "<3.3.3", + "johnbillion/wp-crontrol": "<1.16.2", + "joomla/application": "<1.0.13", + "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/filesystem": "<1.6.2|>=2,<2.0.1", + "joomla/filter": "<1.4.4|>=2,<2.0.1", + "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", + "joomla/input": ">=2,<2.0.2", + "joomla/joomla-cms": ">=2.5,<3.9.12", "joomla/session": "<1.3.1", + "joyqi/hyper-down": "<=2.4.27", + "jsdecena/laracom": "<2.0.9", "jsmitty12/phpwhois": "<5.1", + "juzaweb/cms": "<=3.4", + "jweiland/events2": "<8.3.8|>=9,<9.0.6", "kazist/phpwhois": "<=4.2.6", + "kelvinmo/simplexrd": "<3.1.1", + "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", + "kimai/kimai": "<2.16", + "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", + "klaviyo/magento2-extension": ">=1,<3", + "knplabs/knp-snappy": "<=1.4.2", + "kohana/core": "<3.3.3", + "krayin/laravel-crm": "<1.2.2", "kreait/firebase-php": ">=3.2,<3.8.1", + "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", - "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", - "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", + "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", + "laminas/laminas-http": "<2.14.2", + "laravel/fortify": "<1.11.1", + "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", + "laravel/laravel": ">=5.4,<5.4.22", + "laravel/socialite": ">=1,<2.0.10", + "latte/latte": "<2.10.8", + "lavalite/cms": "<=9|==10.1", + "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", "league/commonmark": "<0.18.3", - "magento/magento1ce": "<1.9.4.3", - "magento/magento1ee": ">=1,<1.14.4.3", - "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", + "league/flysystem": "<1.1.4|>=2,<2.1.1", + "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", + "libreform/libreform": ">=2,<=2.0.8", + "librenms/librenms": "<2017.08.18", + "liftkit/database": "<2.13.2", + "lightsaml/lightsaml": "<1.3.5", + "limesurvey/limesurvey": "<3.27.19", + "livehelperchat/livehelperchat": "<=3.91", + "livewire/livewire": ">2.2.4,<2.2.6|>=3.3.5,<3.4.9", + "lms/routes": "<2.1.1", + "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "luyadev/yii-helpers": "<1.2.1", + "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch8|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch6|==2.4.7", + "magento/core": "<=1.9.4.5", + "magento/magento1ce": "<1.9.4.3-dev", + "magento/magento1ee": ">=1,<1.14.4.3-dev", + "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", + "magneto/core": "<1.9.4.4-dev", + "maikuolan/phpmussel": ">=1,<1.6", + "mainwp/mainwp": "<=4.4.3.3", + "mantisbt/mantisbt": "<2.26.2", + "marcwillmann/turn": "<0.3.3", + "matyhtf/framework": "<3.0.6", + "mautic/core": "<4.4.12|>=5.0.0.0-alpha,<5.0.4", + "mdanter/ecc": "<2", + "mediawiki/core": "<1.36.2", + "mediawiki/matomo": "<2.4.3", + "mediawiki/semantic-media-wiki": "<4.0.2", + "melisplatform/melis-asset-manager": "<5.0.1", + "melisplatform/melis-cms": "<5.0.1", + "melisplatform/melis-front": "<5.0.1", + "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", + "mgallegos/laravel-jqgrid": "<=1.3", + "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", + "microsoft/microsoft-graph-beta": "<2.0.1", + "microsoft/microsoft-graph-core": "<2.0.2", + "microweber/microweber": "<=2.0.4", + "mikehaertl/php-shellcommand": "<1.6.1", + "miniorange/miniorange-saml": "<1.4.3", + "mittwald/typo3_forum": "<1.2.1", + "mobiledetect/mobiledetectlib": "<2.8.32", + "modx/revolution": "<=2.8.3.0-patch", + "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", + "moodle/moodle": "<4.3.5|>=4.4.0.0-beta,<4.4.1", + "mos/cimage": "<0.7.19", + "movim/moxl": ">=0.8,<=0.10", + "movingbytes/social-network": "<=1.2.1", + "mpdf/mpdf": "<=7.1.7", + "munkireport/comment": "<4.1", + "munkireport/managedinstalls": "<2.6", + "munkireport/munki_facts": "<1.5", + "munkireport/munkireport": ">=2.5.3,<5.6.3", + "munkireport/reportdata": "<3.5", + "munkireport/softwareupdate": "<1.6", + "mustache/mustache": ">=2,<2.14.1", "namshi/jose": "<2.2", + "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.04", + "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", + "neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/swiftmailer": "<5.4.5", + "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", + "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", + "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", + "nilsteampassnet/teampass": "<3.0.10", + "nonfiction/nterchange": "<4.1.1", + "notrinos/notrinos-erp": "<=0.7", + "noumo/easyii": "<=0.9", + "novaksolutions/infusionsoft-php-sdk": "<1", + "nukeviet/nukeviet": "<4.5.02", + "nyholm/psr7": "<1.6.1", + "nystudio107/craft-seomatic": "<3.4.12", + "nzedb/nzedb": "<0.8", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/backend": "<1.1.2", + "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/october": "<=3.4.4", + "october/rain": "<1.0.472|>=1.1,<1.1.2", + "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.15", + "omeka/omeka-s": "<4.0.3", "onelogin/php-saml": "<2.10.4", + "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.7.4", + "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "oro/crm": ">=1.7,<1.7.4", - "oro/platform": ">=1.7,<1.7.4", + "openmage/magento-lts": "<20.10.1", + "opensolutions/vimbadmin": "<=3.0.15", + "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", + "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", + "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", + "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", + "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", + "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", + "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", + "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", + "oxid-esales/oxideshop-ce": "<4.5", + "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", + "packbackbooks/lti-1-3-php-library": "<5", "padraic/humbug_get_contents": "<1.1.2", - "pagarme/pagarme-php": ">=0,<3", + "pagarme/pagarme-php": "<3", + "pagekit/pagekit": "<=1.0.18", + "paragonie/ecc": "<2.0.1", "paragonie/random_compat": "<2", + "passbolt/passbolt_api": "<4.6.2", + "paypal/adaptivepayments-sdk-php": "<=3.9.2", + "paypal/invoice-sdk-php": "<=3.9", "paypal/merchant-sdk-php": "<3.12", - "pear/archive_tar": "<1.4.4", - "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6", - "phpoffice/phpexcel": "<=1.8.1", - "phpoffice/phpspreadsheet": "<=1.5", + "paypal/permissions-sdk-php": "<=3.9.1", + "pear/archive_tar": "<1.4.14", + "pear/auth": "<1.2.4", + "pear/crypt_gpg": "<1.6.7", + "pear/pear": "<=1.10.1", + "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", + "personnummer/personnummer": "<3.0.2", + "phanan/koel": "<5.1.4", + "phenx/php-svg-lib": "<0.5.2", + "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", + "php-mod/curl": "<2.3.2", + "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpems/phpems": ">=6,<=6.1.3", + "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", + "phpmailer/phpmailer": "<6.5", + "phpmussel/phpmussel": ">=1,<1.6", + "phpmyadmin/phpmyadmin": "<5.2.1", + "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5", + "phpoffice/common": "<0.2.9", + "phpoffice/phpexcel": "<1.8", + "phpoffice/phpspreadsheet": "<1.16", + "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", + "phpservermon/phpservermon": "<3.6", + "phpsysinfo/phpsysinfo": "<3.4.3", "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", - "propel/propel": ">=2-alpha.1,<=2-alpha.7", + "phpxmlrpc/phpxmlrpc": "<4.9.2", + "pi/pi": "<=2.5", + "pimcore/admin-ui-classic-bundle": "<=1.5.1", + "pimcore/customer-management-framework-bundle": "<4.0.6", + "pimcore/data-hub": "<1.2.4", + "pimcore/demo": "<10.3", + "pimcore/ecommerce-framework-bundle": "<1.0.10", + "pimcore/perspective-editor": "<1.5.1", + "pimcore/pimcore": "<11.2.4", + "pixelfed/pixelfed": "<0.11.11", + "plotly/plotly.js": "<2.25.2", + "pocketmine/bedrock-protocol": "<8.0.2", + "pocketmine/pocketmine-mp": "<5.11.2", + "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", + "pressbooks/pressbooks": "<5.18", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockreassurance": "<=5.1.3", + "prestashop/blockwishlist": ">=2,<2.1.1", + "prestashop/contactform": ">=1.0.1,<4.3", + "prestashop/gamification": "<2.3.2", + "prestashop/prestashop": "<8.1.6", + "prestashop/productcomments": "<5.0.2", + "prestashop/ps_emailsubscription": "<2.6.1", + "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_linklist": "<3.1", + "privatebin/privatebin": "<1.4|>=1.5,<1.7.4", + "processwire/processwire": "<=3.0.229", + "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", + "pterodactyl/panel": "<1.11.6", + "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", + "ptrofimov/beanstalk_console": "<1.7.14", + "pubnub/pubnub": "<6.1", "pusher/pusher-php-server": "<2.2.1", + "pwweb/laravel-core": "<=0.3.6.0-beta", + "pyrocms/pyrocms": "<=3.9.1", + "qcubed/qcubed": "<=3.1.1", + "quickapps/cms": "<=2.0.0.0-beta2", + "rainlab/blog-plugin": "<1.4.1", + "rainlab/debugbar-plugin": "<3.1", + "rainlab/user-plugin": "<=1.4.5", + "rankmath/seo-by-rank-math": "<=1.0.95", + "rap2hpoutre/laravel-log-viewer": "<0.13", + "react/http": ">=0.7,<1.9", + "really-simple-plugins/complianz-gdpr": "<6.4.2", + "redaxo/source": "<=5.15.1", + "remdex/livehelperchat": "<4.29", + "reportico-web/reportico": "<=8.1", + "rhukster/dom-sanitizer": "<1.0.7", + "rmccue/requests": ">=1.6,<1.8", "robrichards/xmlseclibs": ">=1,<3.0.4", - "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", - "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", + "roots/soil": "<4.1", + "rudloff/alltube": "<3.0.3", + "s-cart/core": "<6.9", + "s-cart/s-cart": "<6.9", + "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", + "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", + "scheb/two-factor-bundle": "<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", - "shopware/shopware": "<5.3.7", - "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", + "sfroemken/url_redirect": "<=1.2.1", + "sheng/yiicms": "<=1.2", + "shopware/core": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", + "shopware/platform": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", + "shopware/production": "<=6.3.5.2", + "shopware/shopware": "<=5.7.17", + "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", + "shopxo/shopxo": "<=6.1", + "showdoc/showdoc": "<2.10.4", + "silverstripe-australia/advancedreports": ">=1,<=2", + "silverstripe/admin": "<1.13.19|>=2,<2.1.8", + "silverstripe/assets": ">=1,<1.11.1", + "silverstripe/cms": "<4.11.3", + "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": ">=3,<3.6.7|>=3.7,<3.7.3|>=4,<4.4", - "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", + "silverstripe/framework": "<5.2.16", + "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/recipe-cms": ">=4.5,<4.5.3", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", - "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", - "silverstripe/userforms": "<3", + "silverstripe/reports": "<5.2.3", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", + "silverstripe/subsites": ">=2,<2.6.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3|>=5,<5.4.2", + "silverstripe/versioned-admin": ">=1,<1.11.1", "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", - "simplesamlphp/simplesamlphp": "<1.17.8", + "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4|==5.0.0.0-alpha12", + "simplesamlphp/simplesamlphp": "<1.18.6", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplesamlphp/simplesamlphp-module-openid": "<1", + "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplesamlphp/xml-security": "==1.6.11", + "simplito/elliptic-php": "<1.0.6", + "sitegeist/fluid-components": "<3.5", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", "slim/slim": "<2.6", - "smarty/smarty": "<3.1.33", + "slub/slub-events": "<3.0.3", + "smarty/smarty": "<4.5.3|>=5,<5.1.1", + "snipe/snipe-it": "<6.4.2", "socalnick/scn-social-auth": "<1.15.2", + "socialiteproviders/steam": "<1.1", + "spatie/browsershot": "<3.57.4", + "spatie/image-optimizer": "<1.7.3", + "spipu/html2pdf": "<5.2.8", + "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "stormpath/sdk": ">=0,<9.9.99", - "studio-42/elfinder": "<2.1.49", - "swiftmailer/swiftmailer": ">=4,<5.4.5", + "ssddanbrown/bookstack": "<24.05.1", + "statamic/cms": "<4.46|>=5.3,<5.6.2", + "stormpath/sdk": "<9.9.99", + "studio-42/elfinder": "<=2.1.64", + "studiomitte/friendlycaptcha": "<0.1.4", + "subhh/libconnect": "<7.0.8|>=8,<8.1", + "sukohi/surpass": "<1", + "sulu/form-bundle": ">=2,<2.5.3", + "sulu/sulu": "<1.6.44|>=2,<2.4.17|>=2.5,<2.5.13", + "sumocoders/framework-user-bundle": "<1.4", + "superbig/craft-audit": "<3.0.2", + "swag/paypal": "<5.4.4", + "swiftmailer/swiftmailer": "<6.2.5", + "swiftyedit/swiftyedit": "<1.2", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", - "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", - "sylius/resource-bundle": ">=1,<1.3.13|>=1.4,<1.4.6|>=1.5,<1.5.1|>=1.6,<1.6.3", - "sylius/sylius": ">=1,<1.3.12|>=1.4,<1.4.4", + "sylius/grid-bundle": "<1.10.1", + "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", + "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", + "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4", + "symbiote/silverstripe-multivaluefield": ">=3,<3.1", + "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", + "symbiote/silverstripe-seed": "<6.0.3", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfont/process": ">=0", "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", - "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", + "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", "symfony/mime": ">=4.3,<4.3.8", "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", + "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8", - "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8", + "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", + "symfony/symfony": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/translation": ">=2,<2.0.17", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/ux-autocomplete": "<2.11.2", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", - "tecnickcom/tcpdf": "<6.2.22", + "symfony/webhook": ">=6.3,<6.3.8", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symphonycms/symphony-2": "<2.6.4", + "t3/dce": "<0.11.5|>=2.2,<2.6.2", + "t3g/svg-sanitizer": "<1.0.3", + "t3s/content-consent": "<1.0.3|>=2,<2.0.2", + "tastyigniter/tastyigniter": "<3.3", + "tcg/voyager": "<=1.4", + "tecnickcom/tcpdf": "<=6.7.4", + "terminal42/contao-tablelookupwizard": "<3.3.5", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1-beta.1,<2.1.3", + "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", - "titon/framework": ">=0,<9.9.99", + "thinkcmf/thinkcmf": "<6.0.8", + "thorsten/phpmyfaq": "<3.2.2", + "tikiwiki/tiki-manager": "<=17.1", + "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", + "tinymce/tinymce": "<7.2", + "tinymighty/wiki-seo": "<1.2.2", + "titon/framework": "<9.9.99", + "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "topthink/framework": "<6.0.17|>=6.1,<6.1.5|>=8,<8.0.4", + "topthink/think": "<=6.1.1", + "topthink/thinkphp": "<=3.2.3", + "torrentpier/torrentpier": "<=2.4.3", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tribalsystems/zenario": "<9.5.60602", "truckersmp/phpwhois": "<=4.3.1", - "twig/twig": "<1.38|>=2,<2.7", - "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", - "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", - "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", - "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", + "ttskch/pagination-service-provider": "<1", + "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", + "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1", + "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8", + "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<2.6.4", + "userfrosting/userfrosting": ">=0.3.1,<4.6.3", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "uvdesk/community-skeleton": "<=1.1.1", + "uvdesk/core-framework": "<=1.1.1", + "vanilla/safecurl": "<0.9.2", + "verbb/comments": "<1.5.5", + "verbb/formie": "<2.1.6", + "verbb/image-resizer": "<2.0.9", + "verbb/knock-knock": "<1.2.8", + "verot/class.upload.php": "<=2.1.6", + "villagedefrance/opencart-overclocked": "<=1.11.1", + "vova07/yii2-fileapi-widget": "<0.1.9", + "vrana/adminer": "<4.8.1", + "vufind/vufind": ">=2,<9.1.1", + "waldhacker/hcaptcha": "<2.1.2", "wallabag/tcpdf": "<6.2.22", + "wallabag/wallabag": "<2.6.7", + "wanglelecc/laracms": "<=1.0.3", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9", + "web-auth/webauthn-lib": ">=4.5,<4.9", + "web-feet/coastercms": "==5.5", + "webbuilders-group/silverstripe-kapost-bridge": "<0.4", + "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", + "webpa/webpa": "<3.1.2", + "wikibase/wikibase": "<=1.39.3", + "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", - "yiisoft/yii": ">=1.1.14,<1.1.15", - "yiisoft/yii2": "<2.0.15", + "winter/wn-backend-module": "<1.2.4", + "winter/wn-dusk-plugin": "<2.1", + "winter/wn-system-module": "<1.2.4", + "wintercms/winter": "<=1.2.3", + "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", + "wp-cli/wp-cli": ">=0.12,<2.5", + "wp-graphql/wp-graphql": "<=1.14.5", + "wp-premium/gravityforms": "<2.4.21", + "wpanel/wpanel4-cms": "<=4.3.1", + "wpcloud/wp-stateless": "<3.2", + "wpglobus/wpglobus": "<=1.9.6", + "wwbn/avideo": "<14.3", + "xataface/xataface": "<3", + "xpressengine/xpressengine": "<3.0.15", + "yab/quarx": "<2.4.5", + "yeswiki/yeswiki": "<4.1", + "yetiforce/yetiforce-crm": "<=6.4", + "yidashi/yii2cmf": "<=2", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": "<1.1.29", + "yiisoft/yii2": "<2.0.49.4-dev", + "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", - "yiisoft/yii2-dev": "<2.0.15", + "yiisoft/yii2-dev": "<2.0.43", "yiisoft/yii2-elasticsearch": "<2.0.5", - "yiisoft/yii2-gii": "<2.0.4", + "yiisoft/yii2-gii": "<=2.2.4", "yiisoft/yii2-jui": "<2.0.4", "yiisoft/yii2-redis": "<2.0.8", + "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", + "yoast-seo-for-typo3/yoast_seo": "<7.2.3", + "yourls/yourls": "<=1.8.2", + "yuan1994/tpadmin": "<=1.3.12", + "zencart/zencart": "<=1.5.7.0-beta", + "zendesk/zendesk_api_client_php": "<2.2.11", "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5", "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", - "zendframework/zend-diactoros": ">=1,<1.8.4", - "zendframework/zend-feed": ">=1,<2.10.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": ">=1,<2.8.1", + "zendframework/zend-http": "<2.8.1", "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", - "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", "zendframework/zend-validator": ">=2.3,<2.3.6", "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zendframework": "<2.5.1", + "zendframework/zendframework": "<=3", "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", "zendframework/zendxml": ">=1,<1.0.1", + "zenstruck/collection": "<0.2.1", "zetacomponents/mail": "<1.8.2", "zf-commons/zfc-user": "<1.2.2", "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", - "zfr/zfr-oauth2-server-module": "<0.1.2" + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<=6.1.53" }, "type": "metapackage", "notification-url": "https://packagist.org/downloads/", @@ -5595,32 +8754,161 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2020-01-28T17:25:41+00:00" + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "time": "2024-08-05T15:04:41+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "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.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+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": "1.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5640,34 +8928,44 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "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": "3.0.2", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "php": "^7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5680,6 +8978,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -5691,10 +8993,6 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", @@ -5704,33 +9002,100 @@ "compare", "equality" ], - "time": "2018-07-12T15:12:46+00:00" + "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": "3.0.2", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5743,13 +9108,13 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", @@ -5760,27 +9125,37 @@ "unidiff", "unified diff" ], - "time": "2019-02-04T06:01:07+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "4.2.3", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -5788,7 +9163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -5813,34 +9188,44 @@ "environment", "hhvm" ], - "time": "2019-11-20T08:46:58+00:00" + "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": "3.1.2", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5875,35 +9260,45 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], - "time": "2019-09-14T09:02:43+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "3.0.0", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { - "php": "^7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -5911,7 +9306,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5934,34 +9329,101 @@ "keywords": [ "global state" ], - "time": "2019-02-01T05:30:01+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+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": "3.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5981,32 +9443,42 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "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": "1.1.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -6026,32 +9498,42 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "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": "3.0.0", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -6063,45 +9545,58 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, + "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": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "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": "2.0.1", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6121,32 +9616,41 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04T04:07:39+00:00" + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "1.1.3", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", - "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": "^7.2" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -6167,29 +9671,39 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", - "time": "2019-07-02T08:10:15+00:00" + "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": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -6210,48 +9724,51 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "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/browser-kit", - "version": "v5.0.3", + "name": "spatie/array-to-xml", + "version": "3.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "b0294489a7fbb4f3f39c39efe6f0328cb09731b9" + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/b0294489a7fbb4f3f39c39efe6f0328cb09731b9", - "reference": "b0294489a7fbb4f3f39c39efe6f0328cb09731b9", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/dom-crawler": "^4.4|^5.0" + "ext-dom": "*", + "php": "^8.0" }, "require-dev": { - "symfony/css-selector": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0" - }, - "suggest": { - "symfony/process": "" + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Spatie\\ArrayToXml\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6259,44 +9776,62 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/spatie", + "type": "github" } ], - "description": "Symfony BrowserKit Component", - "homepage": "https://symfony.com", - "time": "2020-01-04T14:08:26+00:00" + "time": "2024-05-01T10:20:27+00:00" }, { - "name": "symfony/css-selector", - "version": "v3.4.37", + "name": "symfony/browser-kit", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "e1b3e1a0621d6e48ee46092b4c7d8280f746b3c5" + "url": "https://github.com/symfony/browser-kit.git", + "reference": "9c13742e3175b5815e272b981876ae329bec2040" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/e1b3e1a0621d6e48ee46092b4c7d8280f746b3c5", - "reference": "e1b3e1a0621d6e48ee46092b4c7d8280f746b3c5", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/9c13742e3175b5815e272b981876ae329bec2040", + "reference": "9c13742e3175b5815e272b981876ae329bec2040", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=8.2", + "symfony/dom-crawler": "^6.4|^7.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } + "require-dev": { + "symfony/css-selector": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\CssSelector\\": "" + "Symfony\\Component\\BrowserKit\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6311,52 +9846,53 @@ "name": "Fabien Potencier", "email": "fabien@symfony.com" }, - { - "name": "Jean-FranΓ§ois Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony CssSelector Component", + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", - "time": "2020-01-01T11:03:25+00:00" + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/debug", - "version": "v4.4.4", + "name": "symfony/css-selector", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "20236471058bbaa9907382500fc14005c84601f0" + "url": "https://github.com/symfony/css-selector.git", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/20236471058bbaa9907382500fc14005c84601f0", - "reference": "20236471058bbaa9907382500fc14005c84601f0", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", "shasum": "" }, "require": { - "php": "^7.1.3", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" + "php": ">=8.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\Debug\\": "" + "Symfony\\Component\\CssSelector\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6371,50 +9907,60 @@ "name": "Fabien Potencier", "email": "fabien@symfony.com" }, + { + "name": "Jean-FranΓ§ois Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", - "time": "2020-01-25T12:44:29+00:00" + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/dom-crawler", - "version": "v5.0.3", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "439c3c7be4daa569deef0dd1e30cf3562108d062" + "reference": "01ce8174447f1f1dd33a5854b01beef79061d9fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/439c3c7be4daa569deef0dd1e30cf3562108d062", - "reference": "439c3c7be4daa569deef0dd1e30cf3562108d062", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/01ce8174447f1f1dd33a5854b01beef79061d9fa", + "reference": "01ce8174447f1f1dd33a5854b01beef79061d9fa", "shasum": "" }, "require": { - "php": "^7.2.5", + "masterminds/html5": "^2.6", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, - "conflict": { - "masterminds/html5": "<2.6" - }, "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^4.4|^5.0" - }, - "suggest": { - "symfony/css-selector": "" + "symfony/css-selector": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\DomCrawler\\": "" @@ -6437,44 +9983,81 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DomCrawler Component", + "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", - "time": "2020-01-04T14:08:26+00:00" + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/proxy-manager-bridge", - "version": "v5.0.3", + "name": "symfony/translation", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "04db5e211ec98c3053b37d5cc393677a619e6002" + "url": "https://github.com/symfony/translation.git", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/04db5e211ec98c3053b37d5cc393677a619e6002", - "reference": "04db5e211ec98c3053b37d5cc393677a619e6002", + "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1", "shasum": "" }, "require": { - "ocramius/proxy-manager": "~2.1", - "php": "^7.2.5", - "symfony/dependency-injection": "^5.0" + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { - "zendframework/zend-eventmanager": "2.6.0" + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" }, - "require-dev": { - "symfony/config": "^4.4|^5.0" + "provide": { + "symfony/translation-implementation": "2.3|3.0" }, - "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" }, + "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "Symfony\\Bridge\\ProxyManager\\": "" + "Symfony\\Component\\Translation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6494,106 +10077,107 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ProxyManager Bridge", + "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", - "time": "2020-01-04T14:08:26+00:00" + "support": { + "source": "https://github.com/symfony/translation/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" }, { - "name": "symfony/translation", - "version": "v5.0.3", + "name": "symplify/easy-coding-standard", + "version": "12.3.4", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "28e1054f1ea26c63762d9260c37cb1056ea62dbb" + "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", + "reference": "03cd792d7fa6d9dc59b6e12a5ca73d9873ee9c0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/28e1054f1ea26c63762d9260c37cb1056ea62dbb", - "reference": "28e1054f1ea26c63762d9260c37cb1056ea62dbb", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/03cd792d7fa6d9dc59b6e12a5ca73d9873ee9c0e", + "reference": "03cd792d7fa6d9dc59b6e12a5ca73d9873ee9c0e", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2" + "php": ">=7.2" }, "conflict": { - "symfony/config": "<4.4", - "symfony/dependency-injection": "<5.0", - "symfony/http-kernel": "<5.0", - "symfony/twig-bundle": "<5.0", - "symfony/yaml": "<4.4" - }, - "provide": { - "symfony/translation-implementation": "2.0" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", - "symfony/intl": "^4.4|^5.0", - "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^4.4|^5.0" + "friendsofphp/php-cs-fixer": "<3.46", + "phpcsstandards/php_codesniffer": "<3.8", + "symplify/coding-standard": "<12.1" }, "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" + "ext-dom": "Needed to support checkstyle output format in class CheckstyleOutputFormatter" }, + "bin": [ + "bin/ecs" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", + "keywords": [ + "Code style", + "automation", + "fixer", + "static analysis" + ], + "support": { + "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.3.4" + }, + "funding": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "url": "https://www.paypal.me/rectorphp", + "type": "custom" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/tomasvotruba", + "type": "github" } ], - "description": "Symfony Translation Component", - "homepage": "https://symfony.com", - "time": "2020-01-21T08:40:24+00:00" + "time": "2024-08-01T07:55:09+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.3", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -6613,33 +10197,159 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-06-13T22:48:21+00:00" + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vimeo/psalm", + "version": "5.25.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", + "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.2", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.16", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "nikic/php-parser": "4.17.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^2.0", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "project", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev", + "dev-4.x": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" + }, + "time": "2024-06-16T15:08:35+00:00" }, { "name": "webmozart/assert", - "version": "1.6.0", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", - "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0", - "symfony/polyfill-ctype": "^1.8" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -6661,29 +10371,22 @@ "check", "validate" ], - "time": "2019-11-24T13:36:37+00:00" - } - ], - "aliases": [ - { - "alias": "3.6", - "alias_normalized": "3.6.0.0", - "version": "9999999-dev", - "package": "behat/behat" + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" } ], - "minimum-stability": "stable", + "aliases": [], + "minimum-stability": "RC", "stability-flags": { - "roave/security-advisories": 20, - "behat/behat": 20, - "behat/mink-extension": 20, - "behat/mink-browserkit-driver": 20, - "friends-of-behat/symfony-extension": 10 + "roave/security-advisories": 20 }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "7.4", + "php": "^8.3", "ext-amqp": "*", "ext-apcu": "*", "ext-json": "*", @@ -6692,5 +10395,6 @@ }, "platform-dev": { "ext-xdebug": "*" - } + }, + "plugin-api-version": "2.6.0" } diff --git a/databases/mooc.sql b/databases/mooc.sql deleted file mode 100644 index 6162ace70..000000000 --- a/databases/mooc.sql +++ /dev/null @@ -1,29 +0,0 @@ -CREATE TABLE `courses` ( - `id` CHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `duration` VARCHAR(255) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE `courses_counter` ( - `id` CHAR(36) NOT NULL, - `total` INT NOT NULL, - `existing_courses` JSON NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE `domain_events` ( - `id` CHAR(36) NOT NULL, - `aggregate_id` CHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `body` JSON NOT NULL, - `occurred_on` timestamp NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE `backoffice_courses` ( - `id` CHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `duration` VARCHAR(255) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/docker-compose.yml b/docker-compose.yml index 009164649..19fc0366b 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,51 +1,107 @@ version: '3' services: - mysql: - container_name: codelytv-php_ddd_skeleton-mooc-mysql - image: mysql:8 + shared_rabbitmq: + container_name: codely-php_ddd_skeleton-rabbitmq + image: 'rabbitmq:3.10.5-management' + restart: unless-stopped + ports: + - "5630:5672" + - "8090:15672" + environment: + - RABBITMQ_DEFAULT_USER=codely + - RABBITMQ_DEFAULT_PASS=c0d3ly + + shared_prometheus: + container_name: codely-php_ddd_skeleton-prometheus + image: prom/prometheus:v2.36.1 + volumes: + - ./etc/prometheus/:/etc/prometheus/ + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/usr/share/prometheus/console_libraries' + - '--web.console.templates=/usr/share/prometheus/consoles' + ports: + - "9999:9090" + + mooc_mysql: + container_name: codely-php_ddd_skeleton-mooc-mysql + image: mariadb:10.7.4 ports: - "3360:3306" environment: - MYSQL_ROOT_PASSWORD= - MYSQL_ALLOW_EMPTY_PASSWORD=yes + healthcheck: + test: ["CMD", "mysqladmin", "--user=root", "--password=", "--host=127.0.0.1", "ping", "--silent"] + interval: 2s + timeout: 10s + retries: 10 command: ["--default-authentication-plugin=mysql_native_password"] - rabbitmq: - container_name: codelytv-php_ddd_skeleton-rabbitmq - image: 'rabbitmq:3.7-management' - restart: unless-stopped + backoffice_elasticsearch: + container_name: codely-php_ddd_skeleton-backoffice-elastic + image: docker.elastic.co/elasticsearch/elasticsearch:8.2.3 ports: - - 5630:5672 - - 8090:15672 + - "9200:9200" + - "9300:9300" environment: - - RABBITMQ_DEFAULT_USER=codelytv - - RABBITMQ_DEFAULT_PASS=c0d3ly + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + + backoffice_backend_php: + container_name: codely-php_ddd_skeleton-backoffice_backend-php + user: "${UID}:${GID}" + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + ports: + - "8040:8040" + - "9040:9001" + volumes: + - .:/app:delegated + depends_on: + - shared_rabbitmq + - shared_prometheus + - backoffice_elasticsearch + command: symfony serve --dir=apps/backoffice/backend/public --port=8040 - nginx: - container_name: codelytv-ddd-skeleton-nginx - image: nginx:1.15-alpine + backoffice_frontend_php: + container_name: codely-php_ddd_skeleton-backoffice_frontend-php + user: "${UID}:${GID}" + build: + context: . + dockerfile: Dockerfile restart: unless-stopped ports: - - "8030:80" + - "8041:8041" + - "9041:9001" volumes: - .:/app:delegated - - ./etc/infrastructure/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - - php + - shared_rabbitmq + - shared_prometheus + - backoffice_elasticsearch + - mooc_mysql + command: symfony serve --dir=apps/backoffice/frontend/public --port=8041 - php: - container_name: codelytv-php_ddd_skeleton-php + mooc_backend_php: + container_name: codely-php_ddd_skeleton-mooc_backend-php + user: "${UID}:${GID}" build: context: . dockerfile: Dockerfile restart: unless-stopped ports: - - "9090:9001" + - "8030:8030" + - "9030:9001" volumes: - .:/app:delegated - env_file: - - .env depends_on: - - mysql - - rabbitmq + - shared_rabbitmq + - shared_prometheus + - mooc_mysql + command: symfony serve --dir=apps/mooc/backend/public --port=8030 diff --git a/ecs.php b/ecs.php new file mode 100644 index 000000000..e3ed961ba --- /dev/null +++ b/ecs.php @@ -0,0 +1,26 @@ +paths([__DIR__ . '/apps', __DIR__ . '/src', __DIR__ . '/tests', ]); + + $ecsConfig->sets([CodingStyle::DEFAULT]); + + $ecsConfig->skip([ + FinalClassFixer::class => [ + __DIR__ . '/apps/backoffice/backend/src/BackofficeBackendKernel.php', + __DIR__ . '/apps/backoffice/frontend/src/BackofficeFrontendKernel.php', + __DIR__ . '/apps/mooc/backend/src/MoocBackendKernel.php', + __DIR__ . '/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php', + ], + __DIR__ . '/apps/backoffice/backend/var', + __DIR__ . '/apps/backoffice/frontend/var', + __DIR__ . '/apps/mooc/backend/var', + __DIR__ . '/apps/mooc/frontend/var', + ]); +}; diff --git a/databases/backoffice/courses.json b/etc/databases/backoffice/courses.json similarity index 100% rename from databases/backoffice/courses.json rename to etc/databases/backoffice/courses.json diff --git a/etc/databases/mooc.sql b/etc/databases/mooc.sql new file mode 100644 index 000000000..1a094f827 --- /dev/null +++ b/etc/databases/mooc.sql @@ -0,0 +1,129 @@ +/* ------------------------- + MOOC CONTEXT +---------------------------- */ + +-- Generic tables + +CREATE TABLE mutations ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + table_name VARCHAR(255) NOT NULL, + operation ENUM ('INSERT', 'UPDATE', 'DELETE') NOT NULL, + old_value JSON NULL, + new_value JSON NULL, + mutation_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE domain_events ( + id CHAR(36) NOT NULL, + aggregate_id CHAR(36) NOT NULL, + name VARCHAR(255) NOT NULL, + body JSON NOT NULL, + occurred_on TIMESTAMP NOT NULL, + PRIMARY KEY (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +-- Aggregates tables + +CREATE TABLE courses ( + id CHAR(36) NOT NULL, + name VARCHAR(255) NOT NULL, + duration VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TRIGGER after_courses_insert + AFTER INSERT + ON courses + FOR EACH ROW +BEGIN + INSERT INTO mutations (table_name, operation, new_value, mutation_timestamp) + VALUES ('courses', 'INSERT', JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration), NOW()); +END; + +CREATE TRIGGER after_courses_update + AFTER UPDATE + ON courses + FOR EACH ROW +BEGIN + INSERT INTO mutations (table_name, operation, old_value, new_value, mutation_timestamp) + VALUES ('courses', + 'UPDATE', + JSON_OBJECT('id', old.id, 'name', old.name, 'duration', old.duration), + JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration), + NOW()); +END; + +CREATE TRIGGER after_courses_delete + AFTER DELETE + ON courses + FOR EACH ROW +BEGIN + INSERT INTO mutations (table_name, operation, old_value, mutation_timestamp) + VALUES ('courses', 'DELETE', JSON_OBJECT('id', old.id, 'name', old.name, 'duration', old.duration), NOW()); +END; + +CREATE TABLE courses_counter ( + id CHAR(36) NOT NULL, + total INT NOT NULL, + existing_courses JSON NOT NULL, + PRIMARY KEY (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +INSERT INTO courses_counter (id, total, existing_courses) +VALUES ("cdf26d7d-3deb-4e8c-9f73-4ac085a8d6f3", 0, "[]"); + +CREATE TABLE steps ( + id CHAR(36) NOT NULL, + title VARCHAR(255) NOT NULL, + duration INT NOT NULL, + type VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE steps_video ( + id CHAR(36) NOT NULL, + url VARCHAR(255) NOT NULL, + FOREIGN KEY (id) REFERENCES steps(id) ON DELETE CASCADE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE steps_exercise ( + id CHAR(36) NOT NULL, + content VARCHAR(255) NOT NULL, + FOREIGN KEY (id) REFERENCES steps(id) ON DELETE CASCADE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE steps_quiz ( + id CHAR(36) NOT NULL, + questions TEXT NOT NULL, + FOREIGN KEY (id) REFERENCES steps(id) ON DELETE CASCADE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + + +/* ------------------------- + BACKOFFICE CONTEXT +---------------------------- */ + +CREATE TABLE backoffice_courses ( + id CHAR(36) NOT NULL, + name VARCHAR(255) NOT NULL, + duration VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; diff --git a/doc/endpoints/backoffice_frontend.http b/etc/endpoints/backoffice_frontend.http similarity index 100% rename from doc/endpoints/backoffice_frontend.http rename to etc/endpoints/backoffice_frontend.http diff --git a/etc/endpoints/mooc_backend.http b/etc/endpoints/mooc_backend.http new file mode 100644 index 000000000..070955a99 --- /dev/null +++ b/etc/endpoints/mooc_backend.http @@ -0,0 +1,10 @@ +# Create a course +PUT localhost:8030/courses/{{$uuid}} +Content-Type: application/json + +{ + "name": "The best course", + "duration": "5 hours" +} + +### diff --git a/etc/infrastructure/nginx/default.conf b/etc/infrastructure/nginx/default.conf deleted file mode 100755 index e08bcb5c6..000000000 --- a/etc/infrastructure/nginx/default.conf +++ /dev/null @@ -1,30 +0,0 @@ -server { - listen 80; - server_name localhost api.codelytv.localhost; - root /app/apps/mooc/backend/public; - - error_log stderr; - access_log stdout; - - rewrite ^/index\.php/?(.*)$ /$1 permanent; - - try_files $uri @rewriteapp; - - location @rewriteapp { - rewrite ^(.*)$ /index.php/$1 last; - } - - location ~ /\. { - deny all; - } - - location ~ ^/(index)\.php(/|$) { - fastcgi_split_path_info ^(.+\.php)(/.*)$; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_index index.php; - send_timeout 1800; - fastcgi_read_timeout 1800; - fastcgi_pass php:9000; - } -} diff --git a/etc/infrastructure/php/conf.d/apcu.ini b/etc/infrastructure/php/conf.d/apcu.ini index 3fb1fc345..8fae9c5a0 100644 --- a/etc/infrastructure/php/conf.d/apcu.ini +++ b/etc/infrastructure/php/conf.d/apcu.ini @@ -1,3 +1,5 @@ +apc.enable_cli=1 +apc.enabled=1 apc.shm_segments=1 apc.shm_size=256M apc.num_files_hint=7000 diff --git a/etc/infrastructure/php/conf.d/xdebug.ini b/etc/infrastructure/php/conf.d/xdebug.ini index 4701b2a60..d97b6a059 100644 --- a/etc/infrastructure/php/conf.d/xdebug.ini +++ b/etc/infrastructure/php/conf.d/xdebug.ini @@ -1,15 +1,15 @@ zend_extension = xdebug.so ;Debugging -xdebug.remote_enable = 1; -xdebug.remote_connect_back = 1; -xdebug.remote_autostart = 1; -xdebug.remote_port = 9001; -xdebug.remote_host = host.docker.internal +xdebug.mode=debug +xdebug.start_with_request = yes +xdebug.discover_client_host = yes +xdebug.client_port = 9001 +xdebug.client_host = host.docker.internal ;Profiling -xdebug.profiler_enable = 0; -xdebug.profiler_enable_trigger = 1; -xdebug.profiler_output_dir = "/tmp/xdebug"; +xdebug.mode=profile +xdebug.start_with_request=trigger -xdebug.max_nesting_level = 500; +xdebug.output_dir = "/tmp/xdebug" +xdebug.max_nesting_level = 500 diff --git a/etc/prometheus/prometheus.yml b/etc/prometheus/prometheus.yml new file mode 100644 index 000000000..db505fd9d --- /dev/null +++ b/etc/prometheus/prometheus.yml @@ -0,0 +1,21 @@ +scrape_configs: + + - job_name: 'prometheus' + scrape_interval: 5s + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'backoffice_backend' + scrape_interval: 5s + static_configs: + - targets: ['codelytv-php_ddd_skeleton-backoffice_backend-php:8040'] + + - job_name: 'backoffice_frontend' + scrape_interval: 5s + static_configs: + - targets: ['codelytv-php_ddd_skeleton-backoffice_frontend-php:8041'] + + - job_name: 'mooc_backend' + scrape_interval: 5s + static_configs: + - targets: ['codelytv-php_ddd_skeleton-mooc_backend-php:8030'] diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 000000000..dee5bbe84 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,48 @@ + + + + apps/*/*/var/* + *SimilarComparator* + *IsSimilar* + + src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php + + tests/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php + + tests/Shared/Infrastructure/PhpUnit/UnitTestCase.php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..4404d69b2 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,25 @@ +includes: + - vendor/phpat/phpat/extension.neon + +parameters: + level: 0 + paths: + - ./apps + - ./src + - ./tests + excludePaths: + - ./apps/backoffice/backend/var + - ./apps/backoffice/frontend/var + - ./apps/mooc/backend/var + - ./apps/mooc/frontend/var + +services: + - + class: CodelyTv\Tests\Shared\SharedArchitectureTest + tags: + - phpat.test + + - + class: CodelyTv\Tests\Mooc\MoocArchitectureTest + tags: + - phpat.test diff --git a/phpunit.xml b/phpunit.xml index d6b6663a6..06fda8b8b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,14 +24,19 @@ - - - ./tests/src + + ./tests/Backoffice + + + ./tests/Mooc + + + ./tests/Shared diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 000000000..228b2ab7b --- /dev/null +++ b/psalm.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rector.php b/rector.php new file mode 100644 index 000000000..7ec25f27b --- /dev/null +++ b/rector.php @@ -0,0 +1,27 @@ +paths([ + __DIR__ . '/apps', + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_82, + SetList::TYPE_DECLARATION + ]); + + $rectorConfig->skip([ + __DIR__ . '/apps/backoffice/backend/var', + __DIR__ . '/apps/backoffice/frontend/var', + __DIR__ . '/apps/mooc/backend/var', + __DIR__ . '/apps/mooc/frontend/var', + ]); +}; diff --git a/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php b/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php index 3b0962923..22dfbb7ba 100644 --- a/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php +++ b/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php @@ -1,6 +1,6 @@ repository = $repository; - } + public function store( + AnalyticsDomainEventId $id, + AnalyticsDomainEventAggregateId $aggregateId, + AnalyticsDomainEventName $name, + AnalyticsDomainEventBody $body + ): void { + $domainEvent = new AnalyticsDomainEvent($id, $aggregateId, $name, $body); - public function store( - AnalyticsDomainEventId $id, - AnalyticsDomainEventAggregateId $aggregateId, - AnalyticsDomainEventName $name, - AnalyticsDomainEventBody $body - ): void { - $domainEvent = new AnalyticsDomainEvent($id, $aggregateId, $name, $body); - - $this->repository->save($domainEvent); - } + $this->repository->save($domainEvent); + } } diff --git a/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php b/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php index 1bc858c5f..5607d45ce 100644 --- a/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php +++ b/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php @@ -1,6 +1,6 @@ storer = $storer; - } - - public static function subscribedTo(): array - { - return [DomainEvent::class]; - } - - public function __invoke(DomainEvent $event): void - { - $id = new AnalyticsDomainEventId($event->eventId()); - $aggregateId = new AnalyticsDomainEventAggregateId($event->aggregateId()); - $name = new AnalyticsDomainEventName($event::eventName()); - $body = new AnalyticsDomainEventBody($event->toPrimitives()); - - $this->storer->store($id, $aggregateId, $name, $body); - } + public function __construct(private DomainEventStorer $storer) {} + + public static function subscribedTo(): array + { + return [DomainEvent::class]; + } + + public function __invoke(DomainEvent $event): void + { + $id = new AnalyticsDomainEventId($event->eventId()); + $aggregateId = new AnalyticsDomainEventAggregateId($event->aggregateId()); + $name = new AnalyticsDomainEventName($event::eventName()); + $body = new AnalyticsDomainEventBody($event->toPrimitives()); + + $this->storer->store($id, $aggregateId, $name, $body); + } } diff --git a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php index 479b60bc9..db3fe917e 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php @@ -1,45 +1,15 @@ id = $id; - $this->aggregateId = $aggregateId; - $this->name = $name; - $this->body = $body; - } - - public function id(): AnalyticsDomainEventId - { - return $this->id; - } - - public function aggregateId(): AnalyticsDomainEventAggregateId - { - return $this->aggregateId; - } - - public function name(): AnalyticsDomainEventName - { - return $this->name; - } - - public function body(): AnalyticsDomainEventBody - { - return $this->body; - } + public function __construct( + private AnalyticsDomainEventId $id, + private AnalyticsDomainEventAggregateId $aggregateId, + private AnalyticsDomainEventName $name, + private AnalyticsDomainEventBody $body + ) {} } diff --git a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php index e397b7d1e..8d1bcdfbf 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php @@ -1,11 +1,9 @@ value = $value; - } - - public function value(): array - { - return $this->value; - } + public function value(): array + { + return $this->value; + } } diff --git a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php index 37c294053..6cfebcb7a 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php @@ -1,11 +1,9 @@ username = $username; - $this->password = $password; - } + public function username(): string + { + return $this->username; + } - public function username(): string - { - return $this->username; - } - - public function password(): string - { - return $this->password; - } + public function password(): string + { + return $this->password; + } } diff --git a/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php b/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php index 573bdecf9..6278c023d 100644 --- a/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php +++ b/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php @@ -1,6 +1,6 @@ authenticator = $authenticator; - } + public function __invoke(AuthenticateUserCommand $command): void + { + $username = new AuthUsername($command->username()); + $password = new AuthPassword($command->password()); - public function __invoke(AuthenticateUserCommand $command): void - { - $username = new AuthUsername($command->username()); - $password = new AuthPassword($command->password()); - - $this->authenticator->authenticate($username, $password); - } + $this->authenticator->authenticate($username, $password); + } } diff --git a/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php b/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php index 9ffd1dcb0..99bcf5dfe 100644 --- a/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php +++ b/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php @@ -1,6 +1,6 @@ repository = $repository; - } - - public function authenticate(AuthUsername $username, AuthPassword $password): void - { - $auth = $this->repository->search($username); - - $this->ensureUserExist($auth, $username); - $this->ensureCredentialsAreValid($auth, $password); - } - - private function ensureUserExist(?AuthUser $auth, AuthUsername $username): void - { - if (null === $auth) { - throw new InvalidAuthUsername($username); - } - } - - private function ensureCredentialsAreValid(AuthUser $auth, AuthPassword $password): void - { - if (!$auth->passwordMatches($password)) { - throw new InvalidAuthCredentials($auth->username()); - } - } + public function __construct(private AuthRepository $repository) {} + + public function authenticate(AuthUsername $username, AuthPassword $password): void + { + $auth = $this->repository->search($username); + + if ($auth === null) { + throw new InvalidAuthUsername($username); + } + + $this->ensureCredentialsAreValid($auth, $password); + } + + private function ensureCredentialsAreValid(AuthUser $auth, AuthPassword $password): void + { + if (!$auth->passwordMatches($password)) { + throw new InvalidAuthCredentials($auth->username()); + } + } } diff --git a/src/Backoffice/Auth/Domain/AuthPassword.php b/src/Backoffice/Auth/Domain/AuthPassword.php index 2fdf5f53d..ec76483a7 100644 --- a/src/Backoffice/Auth/Domain/AuthPassword.php +++ b/src/Backoffice/Auth/Domain/AuthPassword.php @@ -1,6 +1,6 @@ value() === $other->value(); - } + public function isEquals(self $other): bool + { + return $this->value() === $other->value(); + } } diff --git a/src/Backoffice/Auth/Domain/AuthRepository.php b/src/Backoffice/Auth/Domain/AuthRepository.php index 887f29394..49088ecd2 100644 --- a/src/Backoffice/Auth/Domain/AuthRepository.php +++ b/src/Backoffice/Auth/Domain/AuthRepository.php @@ -1,10 +1,10 @@ username = $username; - $this->password = $password; - } + public function passwordMatches(AuthPassword $password): bool + { + return $this->password->isEquals($password); + } - public function passwordMatches(AuthPassword $password): bool - { - return $this->password->isEquals($password); - } - - public function username(): AuthUsername - { - return $this->username; - } + public function username(): AuthUsername + { + return $this->username; + } } diff --git a/src/Backoffice/Auth/Domain/AuthUsername.php b/src/Backoffice/Auth/Domain/AuthUsername.php index 87b5133e6..bc1f5b884 100644 --- a/src/Backoffice/Auth/Domain/AuthUsername.php +++ b/src/Backoffice/Auth/Domain/AuthUsername.php @@ -1,11 +1,9 @@ are invalid', $username->value())); - } + public function __construct(AuthUsername $username) + { + parent::__construct(sprintf('The credentials for <%s> are invalid', $username->value())); + } } diff --git a/src/Backoffice/Auth/Domain/InvalidAuthUsername.php b/src/Backoffice/Auth/Domain/InvalidAuthUsername.php index 03c9cb9f0..b120207cb 100644 --- a/src/Backoffice/Auth/Domain/InvalidAuthUsername.php +++ b/src/Backoffice/Auth/Domain/InvalidAuthUsername.php @@ -1,6 +1,6 @@ does not exists', $username->value())); - } + public function __construct(AuthUsername $username) + { + parent::__construct(sprintf('The user <%s> does not exists', $username->value())); + } } diff --git a/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php b/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php index 923c15875..dea981f8a 100644 --- a/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php +++ b/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php @@ -1,6 +1,6 @@ 'barbitas', - 'rafa' => 'pelazo', - ]; + private const USERS = [ + 'javi' => 'barbitas', + 'rafa' => 'pelazo', + ]; - public function search(AuthUsername $username): ?AuthUser - { - $password = get($username->value(), self::USERS); + public function search(AuthUsername $username): ?AuthUser + { + $password = get($username->value(), self::USERS); - return null !== $password ? new AuthUser($username, new AuthPassword($password)) : null; - } + return $password !== null ? new AuthUser($username, new AuthPassword($password)) : null; + } } diff --git a/src/Backoffice/Courses/Application/BackofficeCourseResponse.php b/src/Backoffice/Courses/Application/BackofficeCourseResponse.php index c1e66b09e..d7984b858 100644 --- a/src/Backoffice/Courses/Application/BackofficeCourseResponse.php +++ b/src/Backoffice/Courses/Application/BackofficeCourseResponse.php @@ -1,34 +1,25 @@ id = $id; - $this->name = $name; - $this->duration = $duration; - } + public function id(): string + { + return $this->id; + } - public function id(): string - { - return $this->id; - } + public function name(): string + { + return $this->name; + } - public function name(): string - { - return $this->name; - } - - public function duration(): string - { - return $this->duration; - } + public function duration(): string + { + return $this->duration; + } } diff --git a/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php b/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php index 423278094..489841794 100644 --- a/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php +++ b/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php @@ -1,6 +1,6 @@ courses = $courses; - } + public function __construct(BackofficeCourseResponse ...$courses) + { + $this->courses = $courses; + } - public function courses(): array - { - return $this->courses; - } + public function courses(): array + { + return $this->courses; + } } diff --git a/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php b/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php index d52ccb333..8493f6a46 100644 --- a/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php +++ b/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php @@ -1,23 +1,18 @@ repository = $repository; - } - - public function create(string $id, string $name, string $duration): void - { - $this->repository->save(new BackofficeCourse($id, $name, $duration)); - } + public function create(string $id, string $name, string $duration): void + { + $this->repository->save(new BackofficeCourse($id, $name, $duration)); + } } diff --git a/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php b/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php index c3af5a0ce..06ecd9541 100644 --- a/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php +++ b/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php @@ -1,28 +1,23 @@ creator = $creator; - } + public static function subscribedTo(): array + { + return [CourseCreatedDomainEvent::class]; + } - public static function subscribedTo(): array - { - return [CourseCreatedDomainEvent::class]; - } - - public function __invoke(CourseCreatedDomainEvent $event): void - { - $this->creator->create($event->aggregateId(), $event->name(), $event->duration()); - } + public function __invoke(CourseCreatedDomainEvent $event): void + { + $this->creator->create($event->aggregateId(), $event->name(), $event->duration()); + } } diff --git a/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php b/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php index 2752c10be..8cb541d64 100644 --- a/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php +++ b/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php @@ -1,6 +1,6 @@ repository = $repository; - } + public function __construct(private BackofficeCourseRepository $repository) {} - public function searchAll(): BackofficeCoursesResponse - { - return new BackofficeCoursesResponse(...map($this->toResponse(), $this->repository->searchAll())); - } + public function searchAll(): BackofficeCoursesResponse + { + return new BackofficeCoursesResponse(...map($this->toResponse(), $this->repository->searchAll())); + } - private function toResponse(): callable - { - return static fn(BackofficeCourse $course) => new BackofficeCourseResponse( - $course->id(), - $course->name(), - $course->duration() - ); - } + private function toResponse(): callable + { + return static fn (BackofficeCourse $course): BackofficeCourseResponse => new BackofficeCourseResponse( + $course->id(), + $course->name(), + $course->duration() + ); + } } diff --git a/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php b/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php index f31357e97..41df25386 100644 --- a/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php +++ b/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php @@ -1,11 +1,9 @@ searcher = $searcher; - } - - public function __invoke(SearchAllBackofficeCoursesQuery $query): BackofficeCoursesResponse - { - return $this->searcher->searchAll(); - } + public function __invoke(SearchAllBackofficeCoursesQuery $query): BackofficeCoursesResponse + { + return $this->searcher->searchAll(); + } } diff --git a/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php b/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php index b67bc9b6c..4ed2280a4 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php +++ b/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php @@ -1,6 +1,6 @@ repository = $repository; - } - - public function search(Filters $filters, Order $order, ?int $limit, ?int $offset): BackofficeCoursesResponse - { - $criteria = new Criteria($filters, $order, $offset, $limit); - - return new BackofficeCoursesResponse(...map($this->toResponse(), $this->repository->matching($criteria))); - } - - private function toResponse(): callable - { - return static fn(BackofficeCourse $course) => new BackofficeCourseResponse( - $course->id(), - $course->name(), - $course->duration() - ); - } + public function __construct(private BackofficeCourseRepository $repository) {} + + public function search(Filters $filters, Order $order, ?int $limit, ?int $offset): BackofficeCoursesResponse + { + $criteria = new Criteria($filters, $order, $offset, $limit); + + return new BackofficeCoursesResponse(...map($this->toResponse(), $this->repository->matching($criteria))); + } + + private function toResponse(): callable + { + return static fn (BackofficeCourse $course): BackofficeCourseResponse => new BackofficeCourseResponse( + $course->id(), + $course->name(), + $course->duration() + ); + } } diff --git a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php index dc5aab98f..3833cd4e5 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php +++ b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php @@ -1,55 +1,43 @@ filters = $filters; - $this->orderBy = $orderBy; - $this->order = $order; - $this->limit = $limit; - $this->offset = $offset; - } - - public function filters(): array - { - return $this->filters; - } - - public function orderBy(): ?string - { - return $this->orderBy; - } - - public function order(): ?string - { - return $this->order; - } - - public function limit(): ?int - { - return $this->limit; - } - - public function offset(): ?int - { - return $this->offset; - } + public function __construct( + private array $filters, + private ?string $orderBy, + private ?string $order, + private ?int $limit, + private ?int $offset + ) {} + + public function filters(): array + { + return $this->filters; + } + + public function orderBy(): ?string + { + return $this->orderBy; + } + + public function order(): ?string + { + return $this->order; + } + + public function limit(): ?int + { + return $this->limit; + } + + public function offset(): ?int + { + return $this->offset; + } } diff --git a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php index 7e65788ba..9be731db6 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php +++ b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php @@ -1,6 +1,6 @@ searcher = $searcher; - } + public function __invoke(SearchBackofficeCoursesByCriteriaQuery $query): BackofficeCoursesResponse + { + $filters = Filters::fromValues($query->filters()); + $order = Order::fromValues($query->orderBy(), $query->order()); - public function __invoke(SearchBackofficeCoursesByCriteriaQuery $query): BackofficeCoursesResponse - { - $filters = Filters::fromValues($query->filters()); - $order = Order::fromValues($query->orderBy(), $query->order()); - - return $this->searcher->search($filters, $order, $query->limit(), $query->offset()); - } + return $this->searcher->search($filters, $order, $query->limit(), $query->offset()); + } } diff --git a/src/Backoffice/Courses/Domain/BackofficeCourse.php b/src/Backoffice/Courses/Domain/BackofficeCourse.php index 963a2bafb..85bc4ea53 100644 --- a/src/Backoffice/Courses/Domain/BackofficeCourse.php +++ b/src/Backoffice/Courses/Domain/BackofficeCourse.php @@ -1,6 +1,6 @@ id = $id; - $this->name = $name; - $this->duration = $duration; - } - - public function toPrimitives(): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'duration' => $this->duration, - ]; - } - - public static function fromPrimitives(array $primitives): BackofficeCourse - { - return new self($primitives['id'], $primitives['name'], $primitives['duration']); - } - - public function id(): string - { - return $this->id; - } - - public function name(): string - { - return $this->name; - } - - public function duration(): string - { - return $this->duration; - } + public function __construct(private readonly string $id, private readonly string $name, private readonly string $duration) {} + + public static function fromPrimitives(array $primitives): self + { + return new self($primitives['id'], $primitives['name'], $primitives['duration']); + } + + public function toPrimitives(): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'duration' => $this->duration, + ]; + } + + public function id(): string + { + return $this->id; + } + + public function name(): string + { + return $this->name; + } + + public function duration(): string + { + return $this->duration; + } } diff --git a/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php b/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php index 45016edd3..815a0a6b3 100644 --- a/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php @@ -1,6 +1,6 @@ persist($course->id(), $course->toPrimitives()); - } - - public function searchAll(): array - { - return map($this->toCourse(), $this->searchAllInElastic()); - } - - public function matching(Criteria $criteria): array - { - return map($this->toCourse(), $this->searchByCriteria($criteria)); - } - - protected function aggregateName(): string - { - return 'courses'; - } - - private function toCourse(): callable - { - return static fn(array $primitives) => BackofficeCourse::fromPrimitives($primitives); - } + public function save(BackofficeCourse $course): void + { + $this->persist($course->id(), $course->toPrimitives()); + } + + public function searchAll(): array + { + return map($this->toCourse(), $this->searchAllInElastic()); + } + + public function matching(Criteria $criteria): array + { + return map($this->toCourse(), $this->searchByCriteria($criteria)); + } + + protected function aggregateName(): string + { + return 'courses'; + } + + private function toCourse(): callable + { + return static fn (array $primitives): BackofficeCourse => BackofficeCourse::fromPrimitives($primitives); + } } diff --git a/src/Backoffice/Courses/Infrastructure/Persistence/InMemoryCacheBackofficeCourseRepository.php b/src/Backoffice/Courses/Infrastructure/Persistence/InMemoryCacheBackofficeCourseRepository.php index a089c07c7..d2d84c341 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/InMemoryCacheBackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Infrastructure/Persistence/InMemoryCacheBackofficeCourseRepository.php @@ -1,47 +1,44 @@ repository = $repository; - } - - public function save(BackofficeCourse $course): void - { - $this->repository->save($course); - } - - public function searchAll(): array - { - return empty(self::$allCoursesCache) ? $this->searchAllAndFillCache() : self::$allCoursesCache; - } - - public function matching(Criteria $criteria): array - { - return get($criteria->serialize(), self::$matchingCache) ?: $this->searchMatchingAndFillCache($criteria); - } - - private function searchAllAndFillCache(): array - { - return self::$allCoursesCache = $this->repository->searchAll(); - } - - private function searchMatchingAndFillCache(Criteria $criteria): array - { - return self::$matchingCache[$criteria->serialize()] = $this->repository->matching($criteria); - } + private static array $allCoursesCache = []; + private static array $matchingCache = []; + + public function __construct(private readonly BackofficeCourseRepository $repository) {} + + public function save(BackofficeCourse $course): void + { + $this->repository->save($course); + } + + public function searchAll(): array + { + return empty(self::$allCoursesCache) ? $this->searchAllAndFillCache() : self::$allCoursesCache; + } + + public function matching(Criteria $criteria): array + { + return get($criteria->serialize(), self::$matchingCache) ?: $this->searchMatchingAndFillCache($criteria); + } + + private function searchAllAndFillCache(): array + { + return self::$allCoursesCache = $this->repository->searchAll(); + } + + private function searchMatchingAndFillCache(Criteria $criteria): array + { + return self::$matchingCache[$criteria->serialize()] = $this->repository->matching($criteria); + } } diff --git a/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php b/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php index 57d18379b..46891200b 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php @@ -1,6 +1,6 @@ persist($course); - } + public function save(BackofficeCourse $course): void + { + $this->persist($course); + } - public function searchAll(): array - { - return $this->repository(BackofficeCourse::class)->findAll(); - } + public function searchAll(): array + { + return $this->repository(BackofficeCourse::class)->findAll(); + } - public function matching(Criteria $criteria): array - { - $doctrineCriteria = DoctrineCriteriaConverter::convert($criteria); + public function matching(Criteria $criteria): array + { + $doctrineCriteria = DoctrineCriteriaConverter::convert($criteria); - return $this->repository(BackofficeCourse::class)->matching($doctrineCriteria)->toArray(); - } + return $this->repository(BackofficeCourse::class)->matching($doctrineCriteria)->toArray(); + } } diff --git a/src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml b/src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml index e69de29bb..3260a112e 100644 --- a/src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml +++ b/src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml @@ -0,0 +1,16 @@ +services: + # Databases + # @todo this should be from backoffice, no mooc + Doctrine\ORM\EntityManager: + factory: [ CodelyTv\Mooc\Shared\Infrastructure\Doctrine\MoocEntityManagerFactory, create ] + arguments: + - driver: '%env(MOOC_DATABASE_DRIVER)%' + host: '%env(MOOC_DATABASE_HOST)%' + port: '%env(MOOC_DATABASE_PORT)%' + dbname: '%env(MOOC_DATABASE_NAME)%' + user: '%env(MOOC_DATABASE_USER)%' + password: '%env(MOOC_DATABASE_PASSWORD)%' + - '%env(APP_ENV)%' + tags: + - { name: codely.database_connection } + public: true diff --git a/src/Mooc/Courses/Application/Create/CourseCreator.php b/src/Mooc/Courses/Application/Create/CourseCreator.php index ffbc82823..12b07bed4 100644 --- a/src/Mooc/Courses/Application/Create/CourseCreator.php +++ b/src/Mooc/Courses/Application/Create/CourseCreator.php @@ -1,6 +1,6 @@ repository = $repository; - $this->bus = $bus; - } + public function __invoke(CourseId $id, CourseName $name, CourseDuration $duration): void + { + $course = Course::create($id, $name, $duration); - public function __invoke(CourseId $id, CourseName $name, CourseDuration $duration): void - { - $course = Course::create($id, $name, $duration); - - $this->repository->save($course); - $this->bus->publish(...$course->pullDomainEvents()); - } + $this->repository->save($course); + $this->bus->publish(...$course->pullDomainEvents()); + } } diff --git a/src/Mooc/Courses/Application/Create/CreateCourseCommand.php b/src/Mooc/Courses/Application/Create/CreateCourseCommand.php index 9d55648be..e710c86a9 100644 --- a/src/Mooc/Courses/Application/Create/CreateCourseCommand.php +++ b/src/Mooc/Courses/Application/Create/CreateCourseCommand.php @@ -1,36 +1,27 @@ id = $id; - $this->name = $name; - $this->duration = $duration; - } - - public function id(): string - { - return $this->id; - } - - public function name(): string - { - return $this->name; - } - - public function duration(): string - { - return $this->duration; - } + public function __construct(private string $id, private string $name, private string $duration) {} + + public function id(): string + { + return $this->id; + } + + public function name(): string + { + return $this->name; + } + + public function duration(): string + { + return $this->duration; + } } diff --git a/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php b/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php index d49cbda89..e4685c5cb 100644 --- a/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php +++ b/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php @@ -1,29 +1,24 @@ creator = $creator; - } + public function __invoke(CreateCourseCommand $command): void + { + $id = new CourseId($command->id()); + $name = new CourseName($command->name()); + $duration = new CourseDuration($command->duration()); - public function __invoke(CreateCourseCommand $command): void - { - $id = new CourseId($command->id()); - $name = new CourseName($command->name()); - $duration = new CourseDuration($command->duration()); - - $this->creator->__invoke($id, $name, $duration); - } + $this->creator->__invoke($id, $name, $duration); + } } diff --git a/src/Mooc/Courses/Application/Find/CourseFinder.php b/src/Mooc/Courses/Application/Find/CourseFinder.php index 5b03a076c..695e2aef7 100644 --- a/src/Mooc/Courses/Application/Find/CourseFinder.php +++ b/src/Mooc/Courses/Application/Find/CourseFinder.php @@ -1,31 +1,26 @@ repository = $repository; - } + public function __invoke(CourseId $id): Course + { + $course = $this->repository->search($id); - public function __invoke(CourseId $id): Course - { - $course = $this->repository->search($id); + if ($course === null) { + throw new CourseNotExist($id); + } - if (null === $course) { - throw new CourseNotExist($id); - } - - return $course; - } + return $course; + } } diff --git a/src/Mooc/Courses/Application/Update/CourseRenamer.php b/src/Mooc/Courses/Application/Update/CourseRenamer.php index d39b77387..77c8632d4 100644 --- a/src/Mooc/Courses/Application/Update/CourseRenamer.php +++ b/src/Mooc/Courses/Application/Update/CourseRenamer.php @@ -1,35 +1,31 @@ repository = $repository; - $this->finder = new CourseFinder($repository); - $this->bus = $bus; - } - - public function __invoke(CourseId $id, CourseName $newName): void - { - $course = $this->finder->__invoke($id); - - $course->rename($newName); - - $this->repository->save($course); - $this->bus->publish(...$course->pullDomainEvents()); - } + private CourseFinder $finder; + + public function __construct(private CourseRepository $repository, private EventBus $bus) + { + $this->finder = new CourseFinder($repository); + } + + public function __invoke(CourseId $id, CourseName $newName): void + { + $course = $this->finder->__invoke($id); + + $course->rename($newName); + + $this->repository->save($course); + $this->bus->publish(...$course->pullDomainEvents()); + } } diff --git a/src/Mooc/Courses/Domain/Course.php b/src/Mooc/Courses/Domain/Course.php index 9293759d0..854ecdd4e 100644 --- a/src/Mooc/Courses/Domain/Course.php +++ b/src/Mooc/Courses/Domain/Course.php @@ -1,51 +1,42 @@ id = $id; - $this->name = $name; - $this->duration = $duration; - } - - public static function create(CourseId $id, CourseName $name, CourseDuration $duration): self - { - $course = new self($id, $name, $duration); - - $course->record(new CourseCreatedDomainEvent($id->value(), $name->value(), $duration->value())); - - return $course; - } - - public function id(): CourseId - { - return $this->id; - } - - public function name(): CourseName - { - return $this->name; - } - - public function duration(): CourseDuration - { - return $this->duration; - } - - public function rename(CourseName $newName): void - { - $this->name = $newName; - } + public function __construct(private readonly CourseId $id, private CourseName $name, private readonly CourseDuration $duration) {} + + public static function create(CourseId $id, CourseName $name, CourseDuration $duration): self + { + $course = new self($id, $name, $duration); + + $course->record(new CourseCreatedDomainEvent($id->value(), $name->value(), $duration->value())); + + return $course; + } + + public function id(): CourseId + { + return $this->id; + } + + public function name(): CourseName + { + return $this->name; + } + + public function duration(): CourseDuration + { + return $this->duration; + } + + public function rename(CourseName $newName): void + { + $this->name = $newName; + } } diff --git a/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php b/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php index b7e888a78..69e89f799 100644 --- a/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php +++ b/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php @@ -1,6 +1,6 @@ name = $name; - $this->duration = $duration; - } - - public static function eventName(): string - { - return 'course.created'; - } - - public function toPrimitives(): array - { - return [ - 'name' => $this->name, - 'duration' => $this->duration, - ]; - } - - public static function fromPrimitives( - string $aggregateId, - array $body, - string $eventId, - string $occurredOn - ): DomainEvent { - return new self($aggregateId, $body['name'], $body['duration'], $eventId, $occurredOn); - } - - public function name(): string - { - return $this->name; - } - - public function duration(): string - { - return $this->duration; - } + public function __construct( + string $id, + private readonly string $name, + private readonly string $duration, + string $eventId = null, + string $occurredOn = null + ) { + parent::__construct($id, $eventId, $occurredOn); + } + + public static function eventName(): string + { + return 'course.created'; + } + + public static function fromPrimitives( + string $aggregateId, + array $body, + string $eventId, + string $occurredOn + ): DomainEvent { + return new self($aggregateId, $body['name'], $body['duration'], $eventId, $occurredOn); + } + + public function toPrimitives(): array + { + return [ + 'name' => $this->name, + 'duration' => $this->duration, + ]; + } + + public function name(): string + { + return $this->name; + } + + public function duration(): string + { + return $this->duration; + } } diff --git a/src/Mooc/Courses/Domain/CourseDuration.php b/src/Mooc/Courses/Domain/CourseDuration.php index 1a0124066..71a56484b 100644 --- a/src/Mooc/Courses/Domain/CourseDuration.php +++ b/src/Mooc/Courses/Domain/CourseDuration.php @@ -1,11 +1,9 @@ id = $id; - - parent::__construct(); - } - - public function errorCode(): string - { - return 'course_not_exist'; - } - - protected function errorMessage(): string - { - return sprintf('The course <%s> does not exist', $this->id->value()); - } + public function __construct(private readonly CourseId $id) + { + parent::__construct(); + } + + public function errorCode(): string + { + return 'course_not_exist'; + } + + protected function errorMessage(): string + { + return sprintf('The course <%s> does not exist', $this->id->value()); + } } diff --git a/src/Mooc/Courses/Domain/CourseRepository.php b/src/Mooc/Courses/Domain/CourseRepository.php index b3ebb90f3..338ad9500 100644 --- a/src/Mooc/Courses/Domain/CourseRepository.php +++ b/src/Mooc/Courses/Domain/CourseRepository.php @@ -1,14 +1,14 @@ persist($course); - } + public function save(Course $course): void + { + $this->persist($course); + } - public function search(CourseId $id): ?Course - { - return $this->repository(Course::class)->find($id); - } + public function search(CourseId $id): ?Course + { + return $this->repository(Course::class)->find($id); + } } diff --git a/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php b/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php index c774876cc..b6a15ecbd 100644 --- a/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php +++ b/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php @@ -1,31 +1,31 @@ fileName($course->id()->value()), serialize($course)); - } + public function save(Course $course): void + { + file_put_contents($this->fileName($course->id()->value()), serialize($course)); + } - public function search(CourseId $id): ?Course - { - return file_exists($this->fileName($id->value())) - ? unserialize(file_get_contents($this->fileName($id->value()))) - : null; - } + public function search(CourseId $id): ?Course + { + return file_exists($this->fileName($id->value())) + ? unserialize(file_get_contents($this->fileName($id->value()))) + : null; + } - private function fileName(string $id): string - { - return sprintf('%s.%s.repo', self::FILE_PATH, $id); - } + private function fileName(string $id): string + { + return sprintf('%s.%s.repo', self::FILE_PATH, $id); + } } diff --git a/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php b/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php index b58b96f08..000422643 100644 --- a/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php +++ b/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php @@ -1,29 +1,24 @@ repository = $repository; - } + public function __invoke(): CoursesCounterResponse + { + $counter = $this->repository->search(); - public function __invoke(): CoursesCounterResponse - { - $counter = $this->repository->search(); + if ($counter === null) { + throw new CoursesCounterNotExist(); + } - if (null === $counter) { - throw new CoursesCounterNotExist(); - } - - return new CoursesCounterResponse($counter->total()->value()); - } + return new CoursesCounterResponse($counter->total()->value()); + } } diff --git a/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php b/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php index 87d0a6cfb..21e8a0811 100644 --- a/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php +++ b/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php @@ -1,22 +1,17 @@ total = $total; - } - - public function total(): int - { - return $this->total; - } + public function total(): int + { + return $this->total; + } } diff --git a/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php b/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php index 7cdc8685c..4be728c05 100644 --- a/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php +++ b/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php @@ -1,11 +1,9 @@ finder = $finder; - } - - public function __invoke(FindCoursesCounterQuery $query): CoursesCounterResponse - { - return $this->finder->__invoke(); - } + public function __invoke(FindCoursesCounterQuery $query): CoursesCounterResponse + { + return $this->finder->__invoke(); + } } diff --git a/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php b/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php index 92252abdf..b1131d11d 100644 --- a/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php +++ b/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php @@ -1,46 +1,38 @@ repository = $repository; - $this->uuidGenerator = $uuidGenerator; - $this->bus = $bus; - } - - public function __invoke(CourseId $courseId): void - { - $counter = $this->repository->search() ?: $this->initializeCounter(); - - if (!$counter->hasIncremented($courseId)) { - $counter->increment($courseId); - - $this->repository->save($counter); - $this->bus->publish(...$counter->pullDomainEvents()); - } - } - - private function initializeCounter(): CoursesCounter - { - return CoursesCounter::initialize(new CoursesCounterId($this->uuidGenerator->generate())); - } + public function __construct( + private CoursesCounterRepository $repository, + private UuidGenerator $uuidGenerator, + private EventBus $bus + ) {} + + public function __invoke(CourseId $courseId): void + { + $counter = $this->repository->search() ?: $this->initializeCounter(); + + if (!$counter->hasIncremented($courseId)) { + $counter->increment($courseId); + + $this->repository->save($counter); + $this->bus->publish(...$counter->pullDomainEvents()); + } + } + + private function initializeCounter(): CoursesCounter + { + return CoursesCounter::initialize(new CoursesCounterId($this->uuidGenerator->generate())); + } } diff --git a/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php b/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php index eb08f6d1d..d561d7686 100644 --- a/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php +++ b/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php @@ -1,32 +1,28 @@ incrementer = $incrementer; - } + public function __construct(private CoursesCounterIncrementer $incrementer) {} - public static function subscribedTo(): array - { - return [CourseCreatedDomainEvent::class]; - } + public static function subscribedTo(): array + { + return [CourseCreatedDomainEvent::class]; + } - public function __invoke(CourseCreatedDomainEvent $event): void - { - $courseId = new CourseId($event->aggregateId()); + public function __invoke(CourseCreatedDomainEvent $event): void + { + $courseId = new CourseId($event->aggregateId()); - apply($this->incrementer, [$courseId]); - } + apply($this->incrementer, [$courseId]); + } } diff --git a/src/Mooc/CoursesCounter/Domain/CoursesCounter.php b/src/Mooc/CoursesCounter/Domain/CoursesCounter.php index 56762b2af..3d89a5da0 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounter.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounter.php @@ -1,63 +1,63 @@ id = $id; - $this->total = $total; - $this->existingCourses = $existingCourses; - } - - public static function initialize(CoursesCounterId $id): self - { - return new self($id, CoursesCounterTotal::initialize()); - } - - public function id(): CoursesCounterId - { - return $this->id; - } - - public function total(): CoursesCounterTotal - { - return $this->total; - } - - public function existingCourses(): array - { - return $this->existingCourses; - } - - public function increment(CourseId $courseId): void - { - $this->total = $this->total->increment(); - $this->existingCourses[] = $courseId; - - $this->record(new CoursesCounterIncrementedDomainEvent($this->id()->value(), $this->total()->value())); - } - - public function hasIncremented(CourseId $courseId): bool - { - $existingCourse = search($this->courseIdComparator($courseId), $this->existingCourses()); - - return null !== $existingCourse; - } - - private function courseIdComparator(CourseId $courseId): callable - { - return static fn(CourseId $other) => $courseId->equals($other); - } + private array $existingCourses; + + public function __construct( + private readonly CoursesCounterId $id, + private CoursesCounterTotal $total, + CourseId ...$existingCourses + ) { + $this->existingCourses = $existingCourses; + } + + public static function initialize(CoursesCounterId $id): self + { + return new self($id, CoursesCounterTotal::initialize()); + } + + public function id(): CoursesCounterId + { + return $this->id; + } + + public function total(): CoursesCounterTotal + { + return $this->total; + } + + public function existingCourses(): array + { + return $this->existingCourses; + } + + public function increment(CourseId $courseId): void + { + $this->total = $this->total->increment(); + $this->existingCourses[] = $courseId; + + $this->record(new CoursesCounterIncrementedDomainEvent($this->id()->value(), $this->total()->value())); + } + + public function hasIncremented(CourseId $courseId): bool + { + $existingCourse = search($this->courseIdComparator($courseId), $this->existingCourses()); + + return $existingCourse !== null; + } + + private function courseIdComparator(CourseId $courseId): callable + { + return static fn (CourseId $other): bool => $courseId->equals($other); + } } diff --git a/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php b/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php index e29befccc..16d0461e9 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php @@ -1,11 +1,9 @@ total = $total; - } - - public static function eventName(): string - { - return 'courses_counter.incremented'; - } - - public function toPrimitives(): array - { - return [ - 'total' => $this->total, - ]; - } - - public static function fromPrimitives( - string $aggregateId, - array $body, - string $eventId, - string $occurredOn - ): DomainEvent { - return new self($aggregateId, $body['total'], $eventId, $occurredOn); - } + public function __construct( + string $aggregateId, + private readonly int $total, + string $eventId = null, + string $occurredOn = null + ) { + parent::__construct($aggregateId, $eventId, $occurredOn); + } + + public static function eventName(): string + { + return 'courses_counter.incremented'; + } + + public static function fromPrimitives( + string $aggregateId, + array $body, + string $eventId, + string $occurredOn + ): DomainEvent { + return new self($aggregateId, $body['total'], $eventId, $occurredOn); + } + + public function toPrimitives(): array + { + return [ + 'total' => $this->total, + ]; + } } diff --git a/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php b/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php index 7ccaf2f1a..a882cdc38 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php @@ -1,6 +1,6 @@ value() + 1); - } + public function increment(): self + { + return new self($this->value() + 1); + } } diff --git a/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php b/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php index fe82729da..6a2c5d95a 100644 --- a/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php +++ b/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php @@ -1,6 +1,6 @@ values(), $value), $platform); - } - - public function convertToPHPValue($value, AbstractPlatform $platform) - { - $scalars = parent::convertToPHPValue($value, $platform); - - return map($this->toCourseId(), $scalars); - } - - private function values() - { - return static function (CourseId $id) { - return $id->value(); - }; - } - - private function toCourseId() - { - return static function (string $value) { - return new CourseId($value); - }; - } + public static function customTypeName(): string + { + return 'course_ids'; + } + + public function getName(): string + { + return self::customTypeName(); + } + + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + return parent::convertToDatabaseValue(map(fn (CourseId $id): string => $id->value(), $value), $platform); + } + + public function convertToPHPValue($value, AbstractPlatform $platform): array + { + $scalars = parent::convertToPHPValue($value, $platform); + + return map(fn (string $value): CourseId => new CourseId($value), $scalars); + } } diff --git a/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php b/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php index 53c5fcaff..64622b072 100644 --- a/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php +++ b/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php @@ -1,6 +1,6 @@ persist($counter); - } + public function save(CoursesCounter $counter): void + { + $this->persist($counter); + } - public function search(): ?CoursesCounter - { - return $this->repository(CoursesCounter::class)->findOneBy([]); - } + public function search(): ?CoursesCounter + { + return $this->repository(CoursesCounter::class)->findOneBy([]); + } } diff --git a/src/Mooc/Shared/Domain/Course/CourseId.php b/src/Mooc/Shared/Domain/Course/CourseId.php deleted file mode 100644 index c0c4f53c8..000000000 --- a/src/Mooc/Shared/Domain/Course/CourseId.php +++ /dev/null @@ -1,11 +0,0 @@ -ensureIsValidUrl($value); + + parent::__construct($value); + } + + private function ensureIsValidUrl(string $url): void + { + if (filter_var($url, FILTER_VALIDATE_URL) === false) { + throw new InvalidArgumentException(sprintf('The url <%s> is not well formatted', $url)); + } + } +} diff --git a/src/Mooc/Shared/Infrastructure/Doctrine/DbalTypesSearcher.php b/src/Mooc/Shared/Infrastructure/Doctrine/DbalTypesSearcher.php index 8cf036d71..28823de48 100644 --- a/src/Mooc/Shared/Infrastructure/Doctrine/DbalTypesSearcher.php +++ b/src/Mooc/Shared/Infrastructure/Doctrine/DbalTypesSearcher.php @@ -1,78 +1,69 @@ !in_array($possibleModule, ['.', '..']), - scandir($path) - ); - } - - private static function possibleDbalPaths(string $path): array - { - return map( - static function ($unused, string $module) use ($path) { - $mappingsPath = self::MAPPINGS_PATH; - - return realpath("$path/$module/$mappingsPath"); - }, - array_flip(self::modulesInPath($path)) - ); - } - - private static function isExistingDbalPath(): callable - { - return static fn(string $path) => !empty($path); - } - - private static function namespaceFormatter($baseNamespace): callable - { - return static fn(string $path, string $module) => "$baseNamespace\\$module\Domain"; - } - - private static function dbalClassesSearcher(string $contextName): callable - { - return static function (array $totalNamespaces, string $path) use ($contextName) { - $possibleFiles = scandir($path); - $files = filter( - static fn($file) => Utils::endsWith('Type.php', $file), - $possibleFiles - ); - - $namespaces = map( - static function (string $file) use ($path, $contextName) { - $fullPath = "$path/$file"; - $splittedPath = explode("/src/$contextName/", $fullPath); - - $classWithoutPrefix = str_replace(['.php', '/'], ['', '\\'], $splittedPath[1]); - - return "CodelyTv\\$contextName\\$classWithoutPrefix"; - }, - $files - ); - - return array_merge($totalNamespaces, $namespaces); - }; - } + private const MAPPINGS_PATH = 'Infrastructure/Persistence/Doctrine'; + + public static function inPath(string $path, string $contextName): array + { + $possibleDbalDirectories = self::possibleDbalPaths($path); + $dbalDirectories = filter(self::isExistingDbalPath(), $possibleDbalDirectories); + + return reduce(self::dbalClassesSearcher($contextName), $dbalDirectories, []); + } + + private static function modulesInPath(string $path): array + { + return filter( + static fn (string $possibleModule): bool => !in_array($possibleModule, ['.', '..'], true), + scandir($path) + ); + } + + private static function possibleDbalPaths(string $path): array + { + return map( + static function (mixed $_unused, string $module) use ($path) { + $mappingsPath = self::MAPPINGS_PATH; + + return realpath("$path/$module/$mappingsPath"); + }, + array_flip(self::modulesInPath($path)) + ); + } + + private static function isExistingDbalPath(): callable + { + return static fn (string $path): bool => !empty($path); + } + + private static function dbalClassesSearcher(string $contextName): callable + { + return static function (array $totalNamespaces, string $path) use ($contextName): array { + $possibleFiles = scandir($path); + $files = filter(static fn (string $file): bool => str_ends_with($file, 'Type.php'), $possibleFiles); + + $namespaces = map( + static function (string $file) use ($path, $contextName): string { + $fullPath = "$path/$file"; + $splittedPath = explode("/src/$contextName/", $fullPath); + + $classWithoutPrefix = str_replace(['.php', '/'], ['', '\\'], $splittedPath[1]); + + return "CodelyTv\\$contextName\\$classWithoutPrefix"; + }, + $files + ); + + return array_merge($totalNamespaces, $namespaces); + }; + } } diff --git a/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php b/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php index bdda67ab2..85f65ed63 100644 --- a/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php +++ b/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php @@ -1,6 +1,6 @@ !in_array($possibleModule, ['.', '..']), - scandir($path) - ); - } - - private static function possibleMappingPaths(string $path): array - { - return map( - static function ($unused, string $module) use ($path) { - $mappingsPath = self::MAPPINGS_PATH; - - return realpath("$path/$module/$mappingsPath"); - }, - array_flip(self::modulesInPath($path)) - ); - } - - private static function isExistingMappingPath(): callable - { - return static fn(string $path) => !empty($path); - } - - private static function namespaceFormatter($baseNamespace): callable - { - return static fn(string $path, string $module) => "$baseNamespace\\$module\Domain"; - } + private const MAPPINGS_PATH = 'Infrastructure/Persistence/Doctrine'; + + public static function inPath(string $path, string $baseNamespace): array + { + $possibleMappingDirectories = self::possibleMappingPaths($path); + $mappingDirectories = filter(self::isExistingMappingPath(), $possibleMappingDirectories); + + return array_flip(reindex(self::namespaceFormatter($baseNamespace), $mappingDirectories)); + } + + private static function modulesInPath(string $path): array + { + return filter( + static fn (string $possibleModule): bool => !in_array($possibleModule, ['.', '..'], true), + scandir($path) + ); + } + + private static function possibleMappingPaths(string $path): array + { + return map( + static function (mixed $_unused, string $module) use ($path) { + $mappingsPath = self::MAPPINGS_PATH; + + return realpath("$path/$module/$mappingsPath"); + }, + array_flip(self::modulesInPath($path)) + ); + } + + private static function isExistingMappingPath(): callable + { + return static fn (string $path): bool => !empty($path); + } + + private static function namespaceFormatter(string $baseNamespace): callable + { + return static fn (string $path, string $module): string => "$baseNamespace\\$module\Domain"; + } } diff --git a/src/Mooc/Shared/Infrastructure/Doctrine/MoocEntityManagerFactory.php b/src/Mooc/Shared/Infrastructure/Doctrine/MoocEntityManagerFactory.php index dc575b190..71f0b4566 100644 --- a/src/Mooc/Shared/Infrastructure/Doctrine/MoocEntityManagerFactory.php +++ b/src/Mooc/Shared/Infrastructure/Doctrine/MoocEntityManagerFactory.php @@ -1,6 +1,6 @@ questions = $questions; + } +} diff --git a/src/Mooc/Steps/Domain/Quiz/QuizStepQuestion.php b/src/Mooc/Steps/Domain/Quiz/QuizStepQuestion.php new file mode 100644 index 000000000..157172596 --- /dev/null +++ b/src/Mooc/Steps/Domain/Quiz/QuizStepQuestion.php @@ -0,0 +1,22 @@ +question . '----' . implode('****', $this->answers); + } +} diff --git a/src/Mooc/Steps/Domain/Step.php b/src/Mooc/Steps/Domain/Step.php new file mode 100644 index 000000000..b59277cde --- /dev/null +++ b/src/Mooc/Steps/Domain/Step.php @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Exercise.ExerciseStepContent.orm.xml b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Exercise.ExerciseStepContent.orm.xml new file mode 100644 index 000000000..09e493da9 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Exercise.ExerciseStepContent.orm.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Quiz.QuizStep.orm.xml b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Quiz.QuizStep.orm.xml new file mode 100644 index 000000000..4b116a567 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Quiz.QuizStep.orm.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/QuizStepQuestionsType.php b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/QuizStepQuestionsType.php new file mode 100644 index 000000000..677fee748 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/QuizStepQuestionsType.php @@ -0,0 +1,40 @@ + $question->toString(), $value), + $platform + ); + } + + public function convertToPHPValue($value, AbstractPlatform $platform): array + { + $scalars = parent::convertToPHPValue($value, $platform); + + return map(fn (string $value): QuizStepQuestion => QuizStepQuestion::fromString($value), $scalars); + } +} diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Step.orm.xml b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Step.orm.xml new file mode 100644 index 000000000..229add0ca --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Step.orm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/StepDuration.orm.xml b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/StepDuration.orm.xml new file mode 100644 index 000000000..c13079530 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/StepDuration.orm.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/StepIdType.php b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/StepIdType.php new file mode 100644 index 000000000..47b979668 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/StepIdType.php @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Video.VideoStep.orm.xml b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Video.VideoStep.orm.xml new file mode 100644 index 000000000..5302800f2 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Video.VideoStep.orm.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Video.VideoStepUrl.orm.xml b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Video.VideoStepUrl.orm.xml new file mode 100644 index 000000000..a4141ba8f --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/Doctrine/Video.VideoStepUrl.orm.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Mooc/Steps/Infrastructure/Persistence/MySqlStepRepository.php b/src/Mooc/Steps/Infrastructure/Persistence/MySqlStepRepository.php new file mode 100644 index 000000000..45a1151d3 --- /dev/null +++ b/src/Mooc/Steps/Infrastructure/Persistence/MySqlStepRepository.php @@ -0,0 +1,28 @@ +persist($step); + } + + public function search(StepId $id): ?Step + { + return $this->repository(Step::class)->find($id); + } + + public function delete(Step $step): void + { + $this->remove($step); + } +} diff --git a/src/Mooc/Videos/Application/Create/CreateVideoCommand.php b/src/Mooc/Videos/Application/Create/CreateVideoCommand.php new file mode 100644 index 000000000..0a94120f5 --- /dev/null +++ b/src/Mooc/Videos/Application/Create/CreateVideoCommand.php @@ -0,0 +1,43 @@ +id; + } + + public function type(): string + { + return $this->type; + } + + public function title(): string + { + return $this->title; + } + + public function url(): string + { + return $this->url; + } + + public function courseId(): string + { + return $this->courseId; + } +} diff --git a/src/Mooc/Videos/Application/Create/CreateVideoCommandHandler.php b/src/Mooc/Videos/Application/Create/CreateVideoCommandHandler.php new file mode 100644 index 000000000..4e9222d0b --- /dev/null +++ b/src/Mooc/Videos/Application/Create/CreateVideoCommandHandler.php @@ -0,0 +1,28 @@ +id()); + $type = VideoType::from($command->type()); + $title = new VideoTitle($command->title()); + $url = new VideoUrl($command->url()); + $courseId = new CourseId($command->courseId()); + + $this->creator->create($id, $type, $title, $url, $courseId); + } +} diff --git a/src/Mooc/Videos/Application/Create/VideoCreator.php b/src/Mooc/Videos/Application/Create/VideoCreator.php new file mode 100644 index 000000000..a3bb4a193 --- /dev/null +++ b/src/Mooc/Videos/Application/Create/VideoCreator.php @@ -0,0 +1,28 @@ +repository->save($video); + + $this->bus->publish(...$video->pullDomainEvents()); + } +} diff --git a/src/Mooc/Videos/Application/Find/FindVideoQuery.php b/src/Mooc/Videos/Application/Find/FindVideoQuery.php new file mode 100644 index 000000000..5df95d32b --- /dev/null +++ b/src/Mooc/Videos/Application/Find/FindVideoQuery.php @@ -0,0 +1,17 @@ +id; + } +} diff --git a/src/Mooc/Videos/Application/Find/FindVideoQueryHandler.php b/src/Mooc/Videos/Application/Find/FindVideoQueryHandler.php new file mode 100644 index 000000000..c533ce272 --- /dev/null +++ b/src/Mooc/Videos/Application/Find/FindVideoQueryHandler.php @@ -0,0 +1,29 @@ +responseConverter = new VideoResponseConverter(); + } + + public function __invoke(FindVideoQuery $query): VideoResponse + { + $id = new VideoId($query->id()); + + $video = apply($this->finder, [$id]); + + return apply($this->responseConverter, [$video]); + } +} diff --git a/src/Mooc/Videos/Application/Find/VideoFinder.php b/src/Mooc/Videos/Application/Find/VideoFinder.php new file mode 100644 index 000000000..103ff6e75 --- /dev/null +++ b/src/Mooc/Videos/Application/Find/VideoFinder.php @@ -0,0 +1,25 @@ +finder = new DomainVideoFinder($repository); + } + + public function __invoke(VideoId $id): Video + { + return $this->finder->__invoke($id); + } +} diff --git a/src/Mooc/Videos/Application/Find/VideoResponse.php b/src/Mooc/Videos/Application/Find/VideoResponse.php new file mode 100644 index 000000000..fb1061754 --- /dev/null +++ b/src/Mooc/Videos/Application/Find/VideoResponse.php @@ -0,0 +1,18 @@ +id()->value(), + $video->type()->value, + $video->title()->value(), + $video->url()->value(), + $video->courseId()->value() + ); + } +} diff --git a/src/Mooc/Videos/Application/Trim/TrimVideoCommand.php b/src/Mooc/Videos/Application/Trim/TrimVideoCommand.php new file mode 100644 index 000000000..bb2ab5038 --- /dev/null +++ b/src/Mooc/Videos/Application/Trim/TrimVideoCommand.php @@ -0,0 +1,27 @@ +videoId; + } + + public function keepFromSecond(): int + { + return $this->keepFromSecond; + } + + public function keepToSecond(): int + { + return $this->keepToSecond; + } +} diff --git a/src/Mooc/Videos/Application/Trim/TrimVideoCommandHandler.php b/src/Mooc/Videos/Application/Trim/TrimVideoCommandHandler.php new file mode 100644 index 000000000..94cf1e676 --- /dev/null +++ b/src/Mooc/Videos/Application/Trim/TrimVideoCommandHandler.php @@ -0,0 +1,21 @@ +videoId()); + $interval = SecondsInterval::fromValues($command->keepFromSecond(), $command->keepToSecond()); + + $this->trimmer->trim($id, $interval); + } +} diff --git a/src/Mooc/Videos/Application/Trim/VideoTrimmer.php b/src/Mooc/Videos/Application/Trim/VideoTrimmer.php new file mode 100644 index 000000000..75a6d4624 --- /dev/null +++ b/src/Mooc/Videos/Application/Trim/VideoTrimmer.php @@ -0,0 +1,13 @@ +finder = new VideoFinder($repository); + } + + public function __invoke(VideoId $id, VideoTitle $newTitle): void + { + $video = $this->finder->__invoke($id); + + $video->updateTitle($newTitle); + + $this->repository->save($video); + } +} diff --git a/src/Mooc/Videos/Domain/Video.php b/src/Mooc/Videos/Domain/Video.php new file mode 100644 index 000000000..db7a35bdc --- /dev/null +++ b/src/Mooc/Videos/Domain/Video.php @@ -0,0 +1,66 @@ +record( + new VideoCreatedDomainEvent($id->value(), $type->value, $title->value(), $url->value(), $courseId->value()) + ); + + return $video; + } + + public function updateTitle(VideoTitle $newTitle): void + { + $this->title = $newTitle; + } + + public function id(): VideoId + { + return $this->id; + } + + public function type(): VideoType + { + return $this->type; + } + + public function title(): VideoTitle + { + return $this->title; + } + + public function url(): VideoUrl + { + return $this->url; + } + + public function courseId(): CourseId + { + return $this->courseId; + } +} diff --git a/src/Mooc/Videos/Domain/VideoCreatedDomainEvent.php b/src/Mooc/Videos/Domain/VideoCreatedDomainEvent.php new file mode 100644 index 000000000..61cd7f8e8 --- /dev/null +++ b/src/Mooc/Videos/Domain/VideoCreatedDomainEvent.php @@ -0,0 +1,54 @@ + $this->type, + 'title' => $this->title, + 'url' => $this->url, + 'course_id' => $this->courseId, + ]; + } +} diff --git a/src/Mooc/Videos/Domain/VideoFinder.php b/src/Mooc/Videos/Domain/VideoFinder.php new file mode 100644 index 000000000..2386116ef --- /dev/null +++ b/src/Mooc/Videos/Domain/VideoFinder.php @@ -0,0 +1,21 @@ +repository->search($id); + + if ($video === null) { + throw new VideoNotFound($id); + } + + return $video; + } +} diff --git a/src/Mooc/Videos/Domain/VideoId.php b/src/Mooc/Videos/Domain/VideoId.php new file mode 100644 index 000000000..4396d6b85 --- /dev/null +++ b/src/Mooc/Videos/Domain/VideoId.php @@ -0,0 +1,9 @@ + has not been found', $this->id->value()); + } +} diff --git a/src/Mooc/Videos/Domain/VideoRepository.php b/src/Mooc/Videos/Domain/VideoRepository.php new file mode 100644 index 000000000..0cd6aae41 --- /dev/null +++ b/src/Mooc/Videos/Domain/VideoRepository.php @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/Mooc/Videos/Infrastructure/Persistence/Doctrine/VideoIdType.php b/src/Mooc/Videos/Infrastructure/Persistence/Doctrine/VideoIdType.php new file mode 100644 index 000000000..2db8f8d8c --- /dev/null +++ b/src/Mooc/Videos/Infrastructure/Persistence/Doctrine/VideoIdType.php @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/Mooc/Videos/Infrastructure/Persistence/Doctrine/VideoType.orm.xml b/src/Mooc/Videos/Infrastructure/Persistence/Doctrine/VideoType.orm.xml new file mode 100644 index 000000000..b05a427cb --- /dev/null +++ b/src/Mooc/Videos/Infrastructure/Persistence/Doctrine/VideoType.orm.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/Mooc/Videos/Infrastructure/Persistence/VideoRepositoryMySql.php b/src/Mooc/Videos/Infrastructure/Persistence/VideoRepositoryMySql.php new file mode 100644 index 000000000..76e4d8a0c --- /dev/null +++ b/src/Mooc/Videos/Infrastructure/Persistence/VideoRepositoryMySql.php @@ -0,0 +1,42 @@ + 'id', + 'type' => 'type', + 'title' => 'title', + 'url' => 'url', + 'course_id' => 'courseId', + ]; + + public function save(Video $video): void + { + $this->persist($video); + } + + public function search(VideoId $id): ?Video + { + return $this->repository(Video::class)->find($id); + } + + public function searchByCriteria(Criteria $criteria): Videos + { + $doctrineCriteria = DoctrineCriteriaConverter::convert($criteria, self::$criteriaToDoctrineFields); + $videos = $this->repository(Video::class)->matching($doctrineCriteria)->toArray(); + + return new Videos($videos); + } +} diff --git a/src/Shared/Domain/Aggregate/AggregateRoot.php b/src/Shared/Domain/Aggregate/AggregateRoot.php index b517995a6..8162916cf 100644 --- a/src/Shared/Domain/Aggregate/AggregateRoot.php +++ b/src/Shared/Domain/Aggregate/AggregateRoot.php @@ -1,6 +1,6 @@ domainEvents; - $this->domainEvents = []; + final public function pullDomainEvents(): array + { + $domainEvents = $this->domainEvents; + $this->domainEvents = []; - return $domainEvents; - } + return $domainEvents; + } - final protected function record(DomainEvent $domainEvent): void - { - $this->domainEvents[] = $domainEvent; - } + final protected function record(DomainEvent $domainEvent): void + { + $this->domainEvents[] = $domainEvent; + } } diff --git a/src/Shared/Domain/Assert.php b/src/Shared/Domain/Assert.php index fa8ea01e3..d1c5f6e2d 100644 --- a/src/Shared/Domain/Assert.php +++ b/src/Shared/Domain/Assert.php @@ -1,6 +1,6 @@ is not an instance of <%s>', $class, get_class($item)) - ); - } - } - - public static function money($value): void - { - if (!self::isValidMoneyAmount($value)) { - throw new InvalidArgumentException(sprintf('The value <%s> is not a valid money amount', $value)); - } - } - - private static function isValidMoneyAmount($value): bool - { - return is_numeric($value) && $value >= 0; - } + public static function instanceOf(string $class, mixed $item): void + { + if (!$item instanceof $class) { + throw new InvalidArgumentException(sprintf('The object <%s> is not an instance of <%s>', $class, $item::class)); + } + } } diff --git a/src/Shared/Domain/Bus/Command/Command.php b/src/Shared/Domain/Bus/Command/Command.php index cba898037..dceac12c7 100644 --- a/src/Shared/Domain/Bus/Command/Command.php +++ b/src/Shared/Domain/Bus/Command/Command.php @@ -1,9 +1,7 @@ aggregateId = $aggregateId; - $this->eventId = $eventId ?: Uuid::random()->value(); - $this->occurredOn = $occurredOn ?: Utils::dateToString(new DateTimeImmutable()); - } - - abstract public function toPrimitives(): array; - - abstract public static function fromPrimitives( - string $aggregateId, - array $body, - string $eventId, - string $occurredOn - ): self; - - abstract public static function eventName(): string; - - public function aggregateId(): string - { - return $this->aggregateId; - } - - public function eventId(): string - { - return $this->eventId; - } - - public function occurredOn(): string - { - return $this->occurredOn; - } + private readonly string $eventId; + private readonly string $occurredOn; + + public function __construct(private readonly string $aggregateId, string $eventId = null, string $occurredOn = null) + { + $this->eventId = $eventId ?: SimpleUuid::random()->value(); + $this->occurredOn = $occurredOn ?: Utils::dateToString(new DateTimeImmutable()); + } + + abstract public static function fromPrimitives( + string $aggregateId, + array $body, + string $eventId, + string $occurredOn + ): self; + + abstract public static function eventName(): string; + + abstract public function toPrimitives(): array; + + final public function aggregateId(): string + { + return $this->aggregateId; + } + + final public function eventId(): string + { + return $this->eventId; + } + + final public function occurredOn(): string + { + return $this->occurredOn; + } } diff --git a/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php b/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php index 3f18b937a..a94a22bc9 100644 --- a/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php +++ b/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php @@ -1,10 +1,10 @@ */ abstract class Collection implements Countable, IteratorAggregate { - private array $items; - - public function __construct(array $items) - { - Assert::arrayOf($this->type(), $items); - - $this->items = $items; - } - - abstract protected function type(): string; - - public function getIterator(): ArrayIterator - { - return new ArrayIterator($this->items()); - } - - public function count(): int - { - return count($this->items()); - } - - protected function each(callable $fn): void - { - each($fn, $this->items()); - } - - protected function items(): array - { - return $this->items; - } + public function __construct(private readonly array $items) + { + Assert::arrayOf($this->type(), $items); + } + + abstract protected function type(): string; + + final public function getIterator(): Traversable + { + return new ArrayIterator($this->items()); + } + + final public function count(): int + { + return count($this->items()); + } + + protected function items(): array + { + return $this->items; + } } diff --git a/src/Shared/Domain/Criteria/Criteria.php b/src/Shared/Domain/Criteria/Criteria.php index 933aa9120..876c9f441 100644 --- a/src/Shared/Domain/Criteria/Criteria.php +++ b/src/Shared/Domain/Criteria/Criteria.php @@ -1,67 +1,61 @@ filters = $filters; - $this->order = $order; - $this->offset = $offset; - $this->limit = $limit; - } - - public function hasFilters(): bool - { - return $this->filters->count() > 0; - } - - public function hasOrder(): bool - { - return !$this->order->isNone(); - } - - public function plainFilters(): array - { - return $this->filters->filters(); - } - - public function filters(): Filters - { - return $this->filters; - } - - public function order(): Order - { - return $this->order; - } - - public function offset(): ?int - { - return $this->offset; - } - - public function limit(): ?int - { - return $this->limit; - } - - public function serialize(): string - { - return sprintf( - '%s~~%s~~%s~~%s', - $this->filters->serialize(), - $this->order->serialize(), - $this->offset, - $this->limit - ); - } + public function __construct( + private Filters $filters, + private Order $order, + private ?int $offset, + private ?int $limit + ) {} + + public function hasFilters(): bool + { + return $this->filters->count() > 0; + } + + public function hasOrder(): bool + { + return !$this->order->isNone(); + } + + public function plainFilters(): array + { + return $this->filters->filters(); + } + + public function filters(): Filters + { + return $this->filters; + } + + public function order(): Order + { + return $this->order; + } + + public function offset(): ?int + { + return $this->offset; + } + + public function limit(): ?int + { + return $this->limit; + } + + public function serialize(): string + { + return sprintf( + '%s~~%s~~%s~~%s', + $this->filters->serialize(), + $this->order->serialize(), + $this->offset ?? 'none', + $this->limit ?? 'none' + ); + } } diff --git a/src/Shared/Domain/Criteria/Filter.php b/src/Shared/Domain/Criteria/Filter.php index fadf548ee..33f15c365 100644 --- a/src/Shared/Domain/Criteria/Filter.php +++ b/src/Shared/Domain/Criteria/Filter.php @@ -1,48 +1,43 @@ field = $field; - $this->operator = $operator; - $this->value = $value; - } - - public static function fromValues(array $values): self - { - return new self( - new FilterField($values['field']), - new FilterOperator($values['operator']), - new FilterValue($values['value']) - ); - } - - public function field(): FilterField - { - return $this->field; - } - - public function operator(): FilterOperator - { - return $this->operator; - } - - public function value(): FilterValue - { - return $this->value; - } - - public function serialize(): string - { - return sprintf('%s.%s.%s', $this->field->value(), $this->operator->value(), $this->value->value()); - } + public function __construct( + private FilterField $field, + private FilterOperator $operator, + private FilterValue $value + ) {} + + public static function fromValues(array $values): self + { + return new self( + new FilterField($values['field']), + FilterOperator::from($values['operator']), + new FilterValue($values['value']) + ); + } + + public function field(): FilterField + { + return $this->field; + } + + public function operator(): FilterOperator + { + return $this->operator; + } + + public function value(): FilterValue + { + return $this->value; + } + + public function serialize(): string + { + return sprintf('%s.%s.%s', $this->field->value(), $this->operator->value, $this->value->value()); + } } diff --git a/src/Shared/Domain/Criteria/FilterField.php b/src/Shared/Domain/Criteria/FilterField.php index c53383e4d..fcc11e2de 100644 --- a/src/Shared/Domain/Criteria/FilterField.php +++ b/src/Shared/Domain/Criteria/FilterField.php @@ -1,11 +1,9 @@ '; - public const LT = '<'; - public const CONTAINS = 'CONTAINS'; - public const NOT_CONTAINS = 'NOT_CONTAINS'; - private static $containing = [self::CONTAINS, self::NOT_CONTAINS]; - - public static function equal(): self - { - return new self('='); - } - - public function isContaining(): bool - { - return in_array($this->value(), self::$containing, true); - } + case EQUAL = '='; + case NOT_EQUAL = '!='; + case GT = '>'; + case LT = '<'; + case CONTAINS = 'CONTAINS'; + case NOT_CONTAINS = 'NOT_CONTAINS'; - protected function throwExceptionForInvalidValue($value): void - { - throw new InvalidArgumentException(sprintf('The filter <%s> is invalid', $value)); - } + public function isContaining(): bool + { + return in_array($this->value, [self::CONTAINS->value, self::NOT_CONTAINS->value], true); + } } diff --git a/src/Shared/Domain/Criteria/FilterValue.php b/src/Shared/Domain/Criteria/FilterValue.php index 78aef7bd6..5576c10fb 100644 --- a/src/Shared/Domain/Criteria/FilterValue.php +++ b/src/Shared/Domain/Criteria/FilterValue.php @@ -1,11 +1,9 @@ items(), [$filter])); - } - - public function filters(): array - { - return $this->items(); - } - - public function serialize(): string - { - return reduce( - static fn(string $accumulate, Filter $filter) => sprintf('%s^%s', $accumulate, $filter->serialize()), - $this->items(), - '' - ); - } - - private static function filterBuilder(): callable - { - return fn(array $values) => Filter::fromValues($values); - } + public static function fromValues(array $values): self + { + return new self(array_map(self::filterBuilder(), $values)); + } + + private static function filterBuilder(): callable + { + return fn (array $values): Filter => Filter::fromValues($values); + } + + public function add(Filter $filter): self + { + return new self(array_merge($this->items(), [$filter])); + } + + public function filters(): array + { + return $this->items(); + } + + public function serialize(): string + { + return reduce( + static fn (string $accumulate, Filter $filter): string => sprintf('%s^%s', $accumulate, $filter->serialize()), + $this->items(), + '' + ); + } + + protected function type(): string + { + return Filter::class; + } } diff --git a/src/Shared/Domain/Criteria/Order.php b/src/Shared/Domain/Criteria/Order.php index 888141614..dfbc8e016 100644 --- a/src/Shared/Domain/Criteria/Order.php +++ b/src/Shared/Domain/Criteria/Order.php @@ -1,52 +1,48 @@ orderBy = $orderBy; - $this->orderType = $orderType; - } - - public static function createDesc(OrderBy $orderBy): Order - { - return new self($orderBy, OrderType::desc()); - } - - public function orderBy(): OrderBy - { - return $this->orderBy; - } - - public function orderType(): OrderType - { - return $this->orderType; - } - - public static function fromValues(?string $orderBy, ?string $order): Order - { - return null === $orderBy ? self::none() : new Order(new OrderBy($orderBy), new OrderType($order)); - } - - public function isNone(): bool - { - return $this->orderType()->isNone(); - } - - public static function none(): Order - { - return new Order(new OrderBy(''), OrderType::none()); - } - - public function serialize(): string - { - return sprintf('%s.%s', $this->orderBy->value(), $this->orderType->value()); - } + public function __construct(private OrderBy $orderBy, private OrderType $orderType) {} + + public static function createDesc(OrderBy $orderBy): self + { + return new self($orderBy, OrderType::DESC); + } + + public static function fromValues(?string $orderBy, ?string $order): self + { + return ($orderBy === null || $order === null) ? self::none() : new self( + new OrderBy($orderBy), + OrderType::from($order) + ); + } + + public static function none(): self + { + return new self(new OrderBy(''), OrderType::NONE); + } + + public function orderBy(): OrderBy + { + return $this->orderBy; + } + + public function orderType(): OrderType + { + return $this->orderType; + } + + public function isNone(): bool + { + return $this->orderType()->isNone(); + } + + public function serialize(): string + { + return sprintf('%s.%s', $this->orderBy->value(), $this->orderType->value); + } } diff --git a/src/Shared/Domain/Criteria/OrderBy.php b/src/Shared/Domain/Criteria/OrderBy.php index ac0f1a3bf..d2d054cae 100644 --- a/src/Shared/Domain/Criteria/OrderBy.php +++ b/src/Shared/Domain/Criteria/OrderBy.php @@ -1,11 +1,9 @@ equals(self::none()); - } + case ASC = 'asc'; + case DESC = 'desc'; + case NONE = 'none'; - protected function throwExceptionForInvalidValue($value): void - { - throw new InvalidArgumentException($value); - } + public function isNone(): bool + { + return $this->value === self::NONE->value; + } } diff --git a/src/Shared/Domain/DomainError.php b/src/Shared/Domain/DomainError.php index 8b9f9c755..18eec3f93 100644 --- a/src/Shared/Domain/DomainError.php +++ b/src/Shared/Domain/DomainError.php @@ -1,6 +1,6 @@ errorMessage()); - } + public function __construct() + { + parent::__construct($this->errorMessage()); + } - abstract public function errorCode(): string; + abstract public function errorCode(): string; - abstract protected function errorMessage(): string; + abstract protected function errorMessage(): string; } diff --git a/src/Shared/Domain/Logger.php b/src/Shared/Domain/Logger.php index 22579635c..fbaa7710c 100644 --- a/src/Shared/Domain/Logger.php +++ b/src/Shared/Domain/Logger.php @@ -1,14 +1,14 @@ ensureIntervalEndsAfterStart($from, $to); + } + + public static function fromValues(int $from, int $to): self + { + return new self(new Second($from), new Second($to)); + } + + private function ensureIntervalEndsAfterStart(Second $from, Second $to): void + { + if ($from->isBiggerThan($to)) { + throw new DomainException('To is bigger than from'); + } + } +} diff --git a/src/Shared/Domain/Utils.php b/src/Shared/Domain/Utils.php index fbd5050e5..92a9d1117 100644 --- a/src/Shared/Domain/Utils.php +++ b/src/Shared/Domain/Utils.php @@ -1,89 +1,73 @@ format(DateTimeInterface::ATOM); - } - - public static function stringToDate(string $date): DateTimeImmutable - { - return new DateTimeImmutable($date); - } - - public static function jsonEncode(array $values): string - { - return json_encode($values); - } - - public static function jsonDecode(string $json): array - { - $data = json_decode($json, true); - - if (JSON_ERROR_NONE !== json_last_error()) { - throw new RuntimeException('Unable to parse response body into JSON: ' . json_last_error()); - } - - return $data; - } - - public static function toSnakeCase(string $text): string - { - return ctype_lower($text) ? $text : strtolower(preg_replace('/([^A-Z\s])([A-Z])/', "$1_$2", $text)); - } - - public static function toCamelCase(string $text): string - { - return lcfirst(str_replace('_', '', ucwords($text, '_'))); - } - - public static function dot($array, $prepend = ''): array - { - $results = []; - foreach ($array as $key => $value) { - if (is_array($value) && !empty($value)) { - $results = array_merge($results, static::dot($value, $prepend . $key . '.')); - } else { - $results[$prepend . $key] = $value; - } - } - - return $results; - } - - public static function directoriesIn(string $path): array - { - return filter( - static fn(string $possibleModule) => !in_array($possibleModule, ['.', '..']), - scandir($path) - ); - } - - public static function filesIn(string $path, $fileType): array - { - return filter( - static fn(string $possibleModule) => strstr($possibleModule, $fileType), - scandir($path) - ); - } + public static function dateToString(DateTimeInterface $date): string + { + return $date->format(DateTimeInterface::ATOM); + } + + public static function stringToDate(string $date): DateTimeImmutable + { + return new DateTimeImmutable($date); + } + + public static function jsonEncode(array $values): string + { + return json_encode($values, JSON_THROW_ON_ERROR); + } + + public static function jsonDecode(string $json): array + { + return json_decode($json, true, flags: JSON_THROW_ON_ERROR); + } + + public static function toSnakeCase(string $text): string + { + return ctype_lower($text) ? $text : strtolower((string) preg_replace('/([^A-Z\s])([A-Z])/', '$1_$2', $text)); + } + + public static function toCamelCase(string $text): string + { + return lcfirst(str_replace('_', '', ucwords($text, '_'))); + } + + public static function dot(array $array, string $prepend = ''): array + { + $results = []; + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, self::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + public static function filesIn(string $path, string $fileType): array + { + return filter( + static fn (string $possibleModule): false | string => strstr($possibleModule, $fileType), + scandir($path) + ); + } + + public static function iterableToArray(iterable $iterable): array + { + if (is_array($iterable)) { + return $iterable; + } + + return iterator_to_array($iterable); + } } diff --git a/src/Shared/Domain/UuidGenerator.php b/src/Shared/Domain/UuidGenerator.php index 7b41da7e1..1064a6edc 100644 --- a/src/Shared/Domain/UuidGenerator.php +++ b/src/Shared/Domain/UuidGenerator.php @@ -1,10 +1,10 @@ ensureIsBetweenAcceptedValues($value); - - $this->value = $value; - } - - abstract protected function throwExceptionForInvalidValue($value); - - public static function __callStatic(string $name, $args) - { - return new static(self::values()[$name]); - } - - public static function fromString(string $value): Enum - { - return new static($value); - } - - public static function values(): array - { - $class = static::class; - - if (!isset(self::$cache[$class])) { - $reflected = new ReflectionClass($class); - self::$cache[$class] = reindex(self::keysFormatter(), $reflected->getConstants()); - } - - return self::$cache[$class]; - } - - public static function randomValue() - { - return self::values()[array_rand(self::values())]; - } - - public function value() - { - return $this->value; - } - - public function equals(Enum $other): bool - { - return $other == $this; - } - - private function ensureIsBetweenAcceptedValues($value): void - { - if (!in_array($value, static::values(), true)) { - $this->throwExceptionForInvalidValue($value); - } - } - - public static function random(): self - { - return new static(self::randomValue()); - } - - private static function keysFormatter(): callable - { - return static fn($unused, string $key): string => Utils::toCamelCase(strtolower($key)); - } - - public function __toString(): string - { - return (string) $this->value(); - } -} diff --git a/src/Shared/Domain/ValueObject/IntValueObject.php b/src/Shared/Domain/ValueObject/IntValueObject.php index dcab01cce..77e13cd76 100644 --- a/src/Shared/Domain/ValueObject/IntValueObject.php +++ b/src/Shared/Domain/ValueObject/IntValueObject.php @@ -1,25 +1,20 @@ value = $value; - } + final public function value(): int + { + return $this->value; + } - public function value(): int - { - return $this->value; - } - - public function __toString(): string - { - return (string) $this->value(); - } + final public function isBiggerThan(self $other): bool + { + return $this->value() > $other->value(); + } } diff --git a/src/Shared/Domain/ValueObject/SimpleUuid.php b/src/Shared/Domain/ValueObject/SimpleUuid.php new file mode 100644 index 000000000..250f41c40 --- /dev/null +++ b/src/Shared/Domain/ValueObject/SimpleUuid.php @@ -0,0 +1,7 @@ +value = $value; - } - - public function value(): string - { - return $this->value; - } - - public function __toString(): string - { - return $this->value(); - } + final public function value(): string + { + return $this->value; + } } diff --git a/src/Shared/Domain/ValueObject/Uuid.php b/src/Shared/Domain/ValueObject/Uuid.php index dd356b14e..63a30fcc8 100644 --- a/src/Shared/Domain/ValueObject/Uuid.php +++ b/src/Shared/Domain/ValueObject/Uuid.php @@ -1,47 +1,44 @@ ensureIsValidUuid($value); - - $this->value = $value; - } - - public function value(): string - { - return $this->value; - } - - public function equals(Uuid $other): bool - { - return $this->value() === $other->value(); - } - - private function ensureIsValidUuid($id): void - { - if (!RamseyUuid::isValid($id)) { - throw new InvalidArgumentException(sprintf('<%s> does not allow the value <%s>.', static::class, $id)); - } - } - - public static function random(): self - { - return new static(RamseyUuid::uuid4()->toString()); - } - - public function __toString(): string - { - return $this->value(); - } + final public function __construct(protected string $value) + { + $this->ensureIsValidUuid($value); + } + + final public static function random(): self + { + return new static(RamseyUuid::uuid4()->toString()); + } + + final public function value(): string + { + return $this->value; + } + + final public function equals(self $other): bool + { + return $this->value() === $other->value(); + } + + public function __toString(): string + { + return $this->value(); + } + + private function ensureIsValidUuid(string $id): void + { + if (!RamseyUuid::isValid($id)) { + throw new InvalidArgumentException(sprintf('<%s> does not allow the value <%s>.', self::class, $id)); + } + } } diff --git a/src/Shared/Infrastructure/Bus/CallableFirstParameterExtractor.php b/src/Shared/Infrastructure/Bus/CallableFirstParameterExtractor.php index 32a72e30a..23564a9e1 100644 --- a/src/Shared/Infrastructure/Bus/CallableFirstParameterExtractor.php +++ b/src/Shared/Infrastructure/Bus/CallableFirstParameterExtractor.php @@ -1,70 +1,80 @@ getMethod('__invoke'); - - if ($this->hasOnlyOneParameter($method)) { - return $this->firstParameterClassFrom($method); - } - - return null; - } - - public static function forCallables(iterable $callables): array - { - return map(self::unflatten(), reindex(self::classExtractor(new self()), $callables)); - } - - public static function forPipedCallables(iterable $callables): array - { - return reduce(self::pipedCallablesReducer(), $callables, []); - } - - private function firstParameterClassFrom(ReflectionMethod $method): string - { - return $method->getParameters()[0]->getClass()->getName(); - } - - private function hasOnlyOneParameter(ReflectionMethod $method): bool - { - return $method->getNumberOfParameters() === 1; - } - - private static function classExtractor(CallableFirstParameterExtractor $parameterExtractor): callable - { - return static fn(callable $handler): string => $parameterExtractor->extract($handler); - } - - private static function pipedCallablesReducer(): callable - { - return static function ($subscribers, DomainEventSubscriber $subscriber): array { - $subscribedEvents = $subscriber::subscribedTo(); - - foreach ($subscribedEvents as $subscribedEvent) { - $subscribers[$subscribedEvent][] = $subscriber; - } - - return $subscribers; - }; - } - - private static function unflatten(): callable - { - return static fn($value) => [$value]; - } + public static function forCallables(iterable $callables): array + { + return map(self::unflatten(), reindex(self::classExtractor(new self()), $callables)); + } + + public static function forPipedCallables(iterable $callables): array + { + return reduce(self::pipedCallablesReducer(), $callables, []); + } + + private static function classExtractor(self $parameterExtractor): callable + { + return static fn (object $handler): ?string => $parameterExtractor->extract($handler); + } + + private static function pipedCallablesReducer(): callable + { + return static function (array $subscribers, DomainEventSubscriber $subscriber): array { + $subscribedEvents = $subscriber::subscribedTo(); + + foreach ($subscribedEvents as $subscribedEvent) { + $subscribers[$subscribedEvent][] = $subscriber; + } + + return $subscribers; + }; + } + + private static function unflatten(): callable + { + return static fn (mixed $value): array => [$value]; + } + + public function extract(object $class): ?string + { + $reflector = new ReflectionClass($class); + $method = $reflector->getMethod('__invoke'); + + if ($this->hasOnlyOneParameter($method)) { + return $this->firstParameterClassFrom($method); + } + + return null; + } + + private function firstParameterClassFrom(ReflectionMethod $method): string + { + /** @var ReflectionNamedType|null $fistParameterType */ + $fistParameterType = $method->getParameters()[0]->getType(); + + if ($fistParameterType === null) { + throw new LogicException('Missing type hint for the first parameter of __invoke'); + } + + return $fistParameterType->getName(); + } + + private function hasOnlyOneParameter(ReflectionMethod $method): bool + { + return $method->getNumberOfParameters() === 1; + } } diff --git a/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php b/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php index 8a4406640..fdb18e189 100644 --- a/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php +++ b/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php @@ -1,6 +1,6 @@ hasn't a command handler associated"); - } + parent::__construct("The command <$commandClass> hasn't a command handler associated"); + } } - diff --git a/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php b/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php index a68077c7a..4f6bbed0e 100644 --- a/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php +++ b/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php @@ -1,6 +1,6 @@ bus = new MessageBus( - [ - new HandleMessageMiddleware( - new HandlersLocator(CallableFirstParameterExtractor::forCallables($commandHandlers)) - ), - ] - ); - } + public function __construct(iterable $commandHandlers) + { + $this->bus = new MessageBus( + [ + new HandleMessageMiddleware( + new HandlersLocator(CallableFirstParameterExtractor::forCallables($commandHandlers)) + ), + ] + ); + } - public function dispatch(Command $command): void - { - try { - $this->bus->dispatch($command); - } catch (NoHandlerForMessageException $unused) { - throw new CommandNotRegisteredError($command); - } catch (HandlerFailedException $error) { - throw $error->getPrevious(); - } - } + public function dispatch(Command $command): void + { + try { + $this->bus->dispatch($command); + } catch (NoHandlerForMessageException) { + throw new CommandNotRegisteredError($command); + } catch (HandlerFailedException $error) { + throw $error->getPrevious() ?? $error; + } + } } diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php index cc08cab51..3d73fa61b 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php @@ -1,37 +1,27 @@ mapping = $mapping; - } - - public function deserialize(string $domainEvent): DomainEvent - { - $eventData = Utils::jsonDecode($domainEvent); - $eventName = $eventData['data']['type']; - $eventClass = $this->mapping->for($eventName); - - if (null === $eventClass) { - throw new RuntimeException("The event <$eventName> doesn't exist or has no subscribers"); - } - - return $eventClass::fromPrimitives( - $eventData['data']['attributes']['id'], - $eventData['data']['attributes'], - $eventData['data']['id'], - $eventData['data']['occurred_on'] - ); - } + public function __construct(private DomainEventMapping $mapping) {} + + public function deserialize(string $domainEvent): DomainEvent + { + $eventData = Utils::jsonDecode($domainEvent); + $eventName = $eventData['data']['type']; + $eventClass = $this->mapping->for($eventName); + + return $eventClass::fromPrimitives( + $eventData['data']['attributes']['id'], + $eventData['data']['attributes'], + $eventData['data']['id'], + $eventData['data']['occurred_on'] + ); + } } diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php index 810f3ba0d..293e5f996 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php @@ -1,6 +1,6 @@ [ - 'id' => $domainEvent->eventId(), - 'type' => $domainEvent::eventName(), - 'occurred_on' => $domainEvent->occurredOn(), - 'attributes' => array_merge($domainEvent->toPrimitives(), ['id' => $domainEvent->aggregateId()]), - ], - 'meta' => [], - ] - ); - } + public static function serialize(DomainEvent $domainEvent): string + { + return json_encode( + [ + 'data' => [ + 'id' => $domainEvent->eventId(), + 'type' => $domainEvent::eventName(), + 'occurred_on' => $domainEvent->occurredOn(), + 'attributes' => array_merge($domainEvent->toPrimitives(), ['id' => $domainEvent->aggregateId()]), + ], + 'meta' => [], + ] + ); + } } diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php b/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php index 0ddbf3186..697acad8d 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php @@ -1,50 +1,43 @@ mapping = reduce($this->eventsExtractor(), $mapping, []); - } - - public function for(string $name) - { - if (!isset($this->mapping[$name])) { - throw new RuntimeException("The Domain Event Class for <$name> doesn't exists or have no subscribers"); - } - - return $this->mapping[$name]; - } - - public function all() - { - return $this->mapping; - } - - private function eventsExtractor(): callable - { - return fn(array $mapping, DomainEventSubscriber $subscriber) => array_merge( - $mapping, - reindex( - $this->eventNameExtractor(), - $subscriber::subscribedTo() - ) - ); - } - - private function eventNameExtractor(): callable - { - return static fn(string $eventClass): string => $eventClass::eventName(); - } + private array $mapping; + + public function __construct(iterable $mapping) + { + $this->mapping = reduce($this->eventsExtractor(), $mapping, []); + } + + public function for(string $name): string + { + if (!isset($this->mapping[$name])) { + throw new RuntimeException("The Domain Event Class for <$name> doesn't exists or have no subscribers"); + } + + return $this->mapping[$name]; + } + + private function eventsExtractor(): callable + { + return fn (array $mapping, DomainEventSubscriber $subscriber): array => array_merge( + $mapping, + reindex($this->eventNameExtractor(), $subscriber::subscribedTo()) + ); + } + + private function eventNameExtractor(): callable + { + return static fn (string $eventClass): string => $eventClass::eventName(); + } } diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberLocator.php b/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberLocator.php index f533717cf..d39aa7651 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberLocator.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberLocator.php @@ -1,6 +1,6 @@ mapping = iterator_to_array($mapping); - } - - public function allSubscribedTo(string $eventClass): callable - { - $formatted = CallableFirstParameterExtractor::forPipedCallables($this->mapping); - - return $formatted[$eventClass]; - } - - public function withRabbitMqQueueNamed(string $queueName): DomainEventSubscriber - { - $subscriber = search( - static fn(DomainEventSubscriber $subscriber) => RabbitMqQueueNameFormatter::format($subscriber) === - $queueName, - $this->mapping - ); - - if (null === $subscriber) { - throw new RuntimeException("There are no subscribers for the <$queueName> queue"); - } - - return $subscriber; - } - - public function all(): array - { - return $this->mapping; - } + private readonly array $mapping; + + public function __construct(Traversable $mapping) + { + $this->mapping = iterator_to_array($mapping); + } + + public function allSubscribedTo(string $eventClass): array + { + $formatted = CallableFirstParameterExtractor::forPipedCallables($this->mapping); + + return $formatted[$eventClass]; + } + + public function withRabbitMqQueueNamed(string $queueName): callable | DomainEventSubscriber + { + $subscriber = search( + static fn (DomainEventSubscriber $subscriber): bool => RabbitMqQueueNameFormatter::format($subscriber) === + $queueName, + $this->mapping + ); + + if ($subscriber === null) { + throw new RuntimeException("There are no subscribers for the <$queueName> queue"); + } + + return $subscriber; + } + + public function all(): array + { + return $this->mapping; + } } diff --git a/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php b/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php index e9c68c189..db19cb695 100644 --- a/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php +++ b/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php @@ -1,6 +1,6 @@ bus = new MessageBus( - [ - new HandleMessageMiddleware( - new HandlersLocator( - CallableFirstParameterExtractor::forPipedCallables($subscribers) - ) - ), - ] - ); - } + public function __construct(iterable $subscribers) + { + $this->bus = new MessageBus( + [ + new HandleMessageMiddleware( + new HandlersLocator(CallableFirstParameterExtractor::forPipedCallables($subscribers)) + ), + ] + ); + } - public function publish(DomainEvent ...$events): void - { - foreach ($events as $event) { - try { - $this->bus->dispatch($event); - } catch (NoHandlerForMessageException $error) { - } - } - } + public function publish(DomainEvent ...$events): void + { + foreach ($events as $event) { + try { + $this->bus->dispatch($event); + } catch (NoHandlerForMessageException) { + } + } + } } diff --git a/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineDomainEventsConsumer.php b/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineDomainEventsConsumer.php index 639db5fa2..a07e3b09f 100644 --- a/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineDomainEventsConsumer.php +++ b/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineDomainEventsConsumer.php @@ -1,6 +1,6 @@ connection = $entityManager->getConnection(); - $this->eventMapping = $eventMapping; - } + public function __construct(EntityManager $entityManager, private DomainEventMapping $eventMapping) + { + $this->connection = $entityManager->getConnection(); + } - public function consume(callable $subscribers, int $eventsToConsume): void - { - $events = $this->connection - ->executeQuery("SELECT * FROM domain_events ORDER BY occurred_on ASC LIMIT $eventsToConsume") - ->fetchAll(FetchMode::ASSOCIATIVE); + public function consume(callable $subscribers, int $eventsToConsume): void + { + $events = $this->connection + ->executeQuery("SELECT * FROM domain_events ORDER BY occurred_on ASC LIMIT $eventsToConsume") + ->fetchAllAssociative(); - each($this->executeSubscribers($subscribers), $events); + each($this->executeSubscribers($subscribers), $events); - $ids = implode(', ', map($this->idExtractor(), $events)); + $ids = implode(', ', map($this->idExtractor(), $events)); - if (!empty($ids)) { - $this->connection->executeUpdate("DELETE FROM domain_events WHERE id IN ($ids)"); - } - } + if (!empty($ids)) { + $this->connection->executeStatement("DELETE FROM domain_events WHERE id IN ($ids)"); + } + } - private function executeSubscribers(callable $subscribers): callable - { - return function (array $rawEvent) use ($subscribers): void { - try { - $domainEventClass = $this->eventMapping->for($rawEvent['name']); - $domainEvent = $domainEventClass::fromPrimitives( - $rawEvent['aggregate_id'], - Utils::jsonDecode($rawEvent['body']), - $rawEvent['id'], - $this->formatDate($rawEvent['occurred_on']) - ); + private function executeSubscribers(callable $subscribers): callable + { + return function (array $rawEvent) use ($subscribers): void { + try { + $domainEventClass = $this->eventMapping->for($rawEvent['name']); + $domainEvent = $domainEventClass::fromPrimitives( + $rawEvent['aggregate_id'], + Utils::jsonDecode($rawEvent['body']), + $rawEvent['id'], + $this->formatDate($rawEvent['occurred_on']) + ); - $subscribers($domainEvent); - } catch (RuntimeException $error) { - } - }; - } + $subscribers($domainEvent); + } catch (RuntimeException) { + } + }; + } - private function formatDate($stringDate): string - { - return Utils::dateToString(new DateTimeImmutable($stringDate)); - } + private function formatDate(mixed $stringDate): string + { + return Utils::dateToString(new DateTimeImmutable($stringDate)); + } - private function idExtractor(): callable - { - return static fn(array $event): string => "'${event['id']}'"; - } + private function idExtractor(): callable + { + return static fn (array $event): string => "'{$event['id']}'"; + } } diff --git a/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php b/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php index b584c477b..2b21c3897 100644 --- a/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php +++ b/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php @@ -1,6 +1,6 @@ connection = $entityManager->getConnection(); - } - - public function publish(DomainEvent ...$domainEvents): void - { - each($this->publisher(), $domainEvents); - } - - private function publisher(): callable - { - return function (DomainEvent $domainEvent): void { - $id = $this->connection->quote($domainEvent->eventId()); - $aggregateId = $this->connection->quote($domainEvent->aggregateId()); - $name = $this->connection->quote($domainEvent::eventName()); - $body = $this->connection->quote(Utils::jsonEncode($domainEvent->toPrimitives())); - $occurredOn = $this->connection->quote( - Utils::stringToDate($domainEvent->occurredOn())->format(self::DATABASE_TIMESTAMP_FORMAT) - ); - - $this->connection->executeUpdate( - <<connection = $entityManager->getConnection(); + } + + public function publish(DomainEvent ...$events): void + { + each($this->publisher(), $events); + } + + private function publisher(): callable + { + return function (DomainEvent $domainEvent): void { + $id = $this->connection->quote($domainEvent->eventId()); + $aggregateId = $this->connection->quote($domainEvent->aggregateId()); + $name = $this->connection->quote($domainEvent::eventName()); + $body = $this->connection->quote(Utils::jsonEncode($domainEvent->toPrimitives())); + $occurredOn = $this->connection->quote( + Utils::stringToDate($domainEvent->occurredOn())->format(self::DATABASE_TIMESTAMP_FORMAT) + ); + + $this->connection->executeStatement( + <<connection = $connection; - } - - public function configure(string $exchangeName, DomainEventSubscriber ...$subscribers): void - { - $retryExchangeName = RabbitMqExchangeNameFormatter::retry($exchangeName); - $deadLetterExchangeName = RabbitMqExchangeNameFormatter::deadLetter($exchangeName); - - $this->declareExchange($exchangeName); - $this->declareExchange($retryExchangeName); - $this->declareExchange($deadLetterExchangeName); - - $this->declareQueues($exchangeName, $retryExchangeName, $deadLetterExchangeName, ...$subscribers); - } - - private function declareExchange(string $exchangeName): void - { - $exchange = $this->connection->exchange($exchangeName); - $exchange->setType(AMQP_EX_TYPE_TOPIC); - $exchange->setFlags(AMQP_DURABLE); - $exchange->declareExchange(); - } - - private function declareQueues( - string $exchangeName, - string $retryExchangeName, - string $deadLetterExchangeName, - DomainEventSubscriber ...$subscribers - ): void { - each($this->queueDeclarator($exchangeName, $retryExchangeName, $deadLetterExchangeName), $subscribers); - } - - private function queueDeclarator( - string $exchangeName, - string $retryExchangeName, - string $deadLetterExchangeName - ): callable { - return function (DomainEventSubscriber $subscriber) use ( - $exchangeName, - $retryExchangeName, - $deadLetterExchangeName - ) { - $queueName = RabbitMqQueueNameFormatter::format($subscriber); - $retryQueueName = RabbitMqQueueNameFormatter::formatRetry($subscriber); - $deadLetterQueueName = RabbitMqQueueNameFormatter::formatDeadLetter($subscriber); - - $queue = $this->declareQueue($queueName); - $retryQueue = $this->declareQueue($retryQueueName, $exchangeName, $queueName, 1000); - $deadLetterQueue = $this->declareQueue($deadLetterQueueName); - - $queue->bind($exchangeName, $queueName); - $retryQueue->bind($retryExchangeName, $queueName); - $deadLetterQueue->bind($deadLetterExchangeName, $queueName); - - foreach ($subscriber::subscribedTo() as $eventClass) { - $queue->bind($exchangeName, $eventClass::eventName()); - } - }; - } - - private function declareQueue( - string $name, - string $deadLetterExchange = null, - string $deadLetterRoutingKey = null, - int $messageTtl = null - ): AMQPQueue { - $queue = $this->connection->queue($name); - - if (null !== $deadLetterExchange) { - $queue->setArgument('x-dead-letter-exchange', $deadLetterExchange); - } - - if (null !== $deadLetterRoutingKey) { - $queue->setArgument('x-dead-letter-routing-key', $deadLetterRoutingKey); - } - - if (null !== $messageTtl) { - $queue->setArgument('x-message-ttl', $messageTtl); - } - - $queue->setFlags(AMQP_DURABLE); - $queue->declareQueue(); - - return $queue; - } + public function __construct(private RabbitMqConnection $connection) {} + + public function configure(string $exchangeName, DomainEventSubscriber ...$subscribers): void + { + $retryExchangeName = RabbitMqExchangeNameFormatter::retry($exchangeName); + $deadLetterExchangeName = RabbitMqExchangeNameFormatter::deadLetter($exchangeName); + + $this->declareExchange($exchangeName); + $this->declareExchange($retryExchangeName); + $this->declareExchange($deadLetterExchangeName); + + $this->declareQueues($exchangeName, $retryExchangeName, $deadLetterExchangeName, ...$subscribers); + } + + private function declareExchange(string $exchangeName): void + { + $exchange = $this->connection->exchange($exchangeName); + $exchange->setType(AMQP_EX_TYPE_TOPIC); + $exchange->setFlags(AMQP_DURABLE); + $exchange->declareExchange(); + } + + private function declareQueues( + string $exchangeName, + string $retryExchangeName, + string $deadLetterExchangeName, + DomainEventSubscriber ...$subscribers + ): void { + each($this->queueDeclarator($exchangeName, $retryExchangeName, $deadLetterExchangeName), $subscribers); + } + + private function queueDeclarator( + string $exchangeName, + string $retryExchangeName, + string $deadLetterExchangeName + ): callable { + return function (DomainEventSubscriber $subscriber) use ( + $exchangeName, + $retryExchangeName, + $deadLetterExchangeName + ): void { + $queueName = RabbitMqQueueNameFormatter::format($subscriber); + $retryQueueName = RabbitMqQueueNameFormatter::formatRetry($subscriber); + $deadLetterQueueName = RabbitMqQueueNameFormatter::formatDeadLetter($subscriber); + + $queue = $this->declareQueue($queueName); + $retryQueue = $this->declareQueue($retryQueueName, $exchangeName, $queueName, 1000); + $deadLetterQueue = $this->declareQueue($deadLetterQueueName); + + $queue->bind($exchangeName, $queueName); + $retryQueue->bind($retryExchangeName, $queueName); + $deadLetterQueue->bind($deadLetterExchangeName, $queueName); + + foreach ($subscriber::subscribedTo() as $eventClass) { + $queue->bind($exchangeName, $eventClass::eventName()); + } + }; + } + + private function declareQueue( + string $name, + string $deadLetterExchange = null, + string $deadLetterRoutingKey = null, + int $messageTtl = null + ): AMQPQueue { + $queue = $this->connection->queue($name); + + if ($deadLetterExchange !== null) { + $queue->setArgument('x-dead-letter-exchange', $deadLetterExchange); + } + + if ($deadLetterRoutingKey !== null) { + $queue->setArgument('x-dead-letter-routing-key', $deadLetterRoutingKey); + } + + if ($messageTtl !== null) { + $queue->setArgument('x-message-ttl', $messageTtl); + } + + $queue->setFlags(AMQP_DURABLE); + $queue->declareQueue(); + + return $queue; + } } diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConnection.php b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConnection.php index 90bb6a50d..ea3bcb9fd 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConnection.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConnection.php @@ -1,6 +1,6 @@ configuration = $configuration; - } - - public function queue(string $name): AMQPQueue - { - if (!array_key_exists($name, self::$queues)) { - $queue = new AMQPQueue($this->channel()); - $queue->setName($name); - - self::$queues[$name] = $queue; - } - - return self::$queues[$name]; - } - - public function exchange(string $name): AMQPExchange - { - if (!array_key_exists($name, self::$exchanges)) { - $exchange = new AMQPExchange($this->channel()); - $exchange->setName($name); - - self::$exchanges[$name] = $exchange; - } - - return self::$exchanges[$name]; - } - - private function channel(): AMQPChannel - { - return self::$channel = self::$channel && self::$channel->isConnected() - ? self::$channel - : new AMQPChannel($this->connection()); - } - - private function connection(): AMQPConnection - { - self::$connection = self::$connection ?: new AMQPConnection($this->configuration); - - if (!self::$connection->isConnected()) { - self::$connection->pconnect(); - } - - return self::$connection; - } + private static ?AMQPConnection $connection = null; + private static ?AMQPChannel $channel = null; + /** @var AMQPExchange[] */ + private static array $exchanges = []; + /** @var AMQPQueue[] */ + private static array $queues = []; + + public function __construct(private readonly array $configuration) {} + + public function queue(string $name): AMQPQueue + { + if (!array_key_exists($name, self::$queues)) { + $queue = new AMQPQueue($this->channel()); + $queue->setName($name); + + self::$queues[$name] = $queue; + } + + return self::$queues[$name]; + } + + public function exchange(string $name): AMQPExchange + { + if (!array_key_exists($name, self::$exchanges)) { + $exchange = new AMQPExchange($this->channel()); + $exchange->setName($name); + + self::$exchanges[$name] = $exchange; + } + + return self::$exchanges[$name]; + } + + private function channel(): AMQPChannel + { + if (!self::$channel?->isConnected()) { + self::$channel = new AMQPChannel($this->connection()); + } + + return self::$channel; + } + + private function connection(): AMQPConnection + { + if (self::$connection === null) { + self::$connection = new AMQPConnection($this->configuration); + } + + if (!self::$connection->isConnected()) { + self::$connection->pconnect(); + } + + return self::$connection; + } } diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.php b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.php index 1abd2f4a7..ea18ec7ce 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.php @@ -1,101 +1,93 @@ connection = $connection; - $this->deserializer = $deserializer; - $this->exchangeName = $exchangeName; - $this->maxRetries = $maxRetries; - } - - public function consume(callable $subscriber, string $queueName): void - { - try { - $this->connection->queue($queueName)->consume($this->consumer($subscriber)); - } catch (AMQPQueueException $error) { - // We don't want to raise an error if there are no messages in the queue - } - } - - private function consumer(callable $subscriber): callable - { - return function (AMQPEnvelope $envelope, AMQPQueue $queue) use ($subscriber) { - $event = $this->deserializer->deserialize($envelope->getBody()); - - try { - $subscriber($event); - } catch (Throwable $error) { - $this->handleConsumptionError($envelope, $queue); - - throw $error; - } - - $queue->ack($envelope->getDeliveryTag()); - }; - } - - private function handleConsumptionError(AMQPEnvelope $envelope, AMQPQueue $queue): void - { - $this->hasBeenRedeliveredTooMuch($envelope) - ? $this->sendToDeadLetter($envelope, $queue) - : $this->sendToRetry($envelope, $queue); - - $queue->ack($envelope->getDeliveryTag()); - } - - private function hasBeenRedeliveredTooMuch(AMQPEnvelope $envelope): bool - { - return get('redelivery_count', $envelope->getHeaders(), 0) >= $this->maxRetries; - } - - private function sendToDeadLetter(AMQPEnvelope $envelope, AMQPQueue $queue): void - { - $this->sendMessageTo(RabbitMqExchangeNameFormatter::deadLetter($this->exchangeName), $envelope, $queue); - } - - private function sendToRetry(AMQPEnvelope $envelope, AMQPQueue $queue): void - { - $this->sendMessageTo(RabbitMqExchangeNameFormatter::retry($this->exchangeName), $envelope, $queue); - } - - private function sendMessageTo(string $exchangeName, AMQPEnvelope $envelope, AMQPQueue $queue): void - { - $headers = $envelope->getHeaders(); - - $this->connection->exchange($exchangeName)->publish( - $envelope->getBody(), - $queue->getName(), - AMQP_NOPARAM, - [ - 'message_id' => $envelope->getMessageId(), - 'content_type' => $envelope->getContentType(), - 'content_encoding' => $envelope->getContentEncoding(), - 'priority' => $envelope->getPriority(), - 'headers' => assoc($headers, 'redelivery_count', get('redelivery_count', $headers, 0) + 1), - ] - ); - } + public function __construct( + private RabbitMqConnection $connection, + private DomainEventJsonDeserializer $deserializer, + private string $exchangeName, + private int $maxRetries + ) {} + + public function consume(callable | DomainEventSubscriber $subscriber, string $queueName): void + { + try { + $this->connection->queue($queueName)->consume($this->consumer($subscriber)); + } catch (AMQPQueueException) { + // We don't want to raise an error if there are no messages in the queue + } + } + + private function consumer(callable $subscriber): callable + { + return function (AMQPEnvelope $envelope, AMQPQueue $queue) use ($subscriber): void { + $event = $this->deserializer->deserialize($envelope->getBody()); + + try { + $subscriber($event); + } catch (Throwable $error) { + $this->handleConsumptionError($envelope, $queue); + + throw $error; + } + + $queue->ack($envelope->getDeliveryTag()); + }; + } + + private function handleConsumptionError(AMQPEnvelope $envelope, AMQPQueue $queue): void + { + $this->hasBeenRedeliveredTooMuch($envelope) + ? $this->sendToDeadLetter($envelope, $queue) + : $this->sendToRetry($envelope, $queue); + + $queue->ack($envelope->getDeliveryTag()); + } + + private function hasBeenRedeliveredTooMuch(AMQPEnvelope $envelope): bool + { + return get('redelivery_count', $envelope->getHeaders(), 0) >= $this->maxRetries; + } + + private function sendToDeadLetter(AMQPEnvelope $envelope, AMQPQueue $queue): void + { + $this->sendMessageTo(RabbitMqExchangeNameFormatter::deadLetter($this->exchangeName), $envelope, $queue); + } + + private function sendToRetry(AMQPEnvelope $envelope, AMQPQueue $queue): void + { + $this->sendMessageTo(RabbitMqExchangeNameFormatter::retry($this->exchangeName), $envelope, $queue); + } + + private function sendMessageTo(string $exchangeName, AMQPEnvelope $envelope, AMQPQueue $queue): void + { + $headers = $envelope->getHeaders(); + + $this->connection->exchange($exchangeName)->publish( + $envelope->getBody(), + $queue->getName(), + AMQP_NOPARAM, + [ + 'message_id' => $envelope->getMessageId(), + 'content_type' => $envelope->getContentType(), + 'content_encoding' => $envelope->getContentEncoding(), + 'priority' => $envelope->getPriority(), + 'headers' => assoc($headers, 'redelivery_count', get('redelivery_count', $headers, 0) + 1), + ] + ); + } } diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php index 91475e825..1d7bc1a89 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php @@ -1,6 +1,6 @@ connection = $connection; - $this->exchangeName = $exchangeName; - $this->failoverPublisher = $failoverPublisher; - } - - public function publish(DomainEvent ...$events): void - { - each($this->publisher(), $events); - } - - private function publisher(): callable - { - return function (DomainEvent $event) { - try { - $this->publishEvent($event); - } catch (AMQPException $error) { - $this->failoverPublisher->publish($event); - } - }; - } - - private function publishEvent(DomainEvent $event): void - { - $body = DomainEventJsonSerializer::serialize($event); - $routingKey = $event::eventName(); - $messageId = $event->eventId(); - - $this->connection->exchange($this->exchangeName)->publish( - $body, - $routingKey, - AMQP_NOPARAM, - [ - 'message_id' => $messageId, - 'content_type' => 'application/json', - 'content_encoding' => 'utf-8', - ] - ); - } + public function __construct( + private RabbitMqConnection $connection, + private string $exchangeName, + private MySqlDoctrineEventBus $failoverPublisher + ) {} + + public function publish(DomainEvent ...$events): void + { + each($this->publisher(), $events); + } + + private function publisher(): callable + { + return function (DomainEvent $event): void { + try { + $this->publishEvent($event); + } catch (AMQPException) { + $this->failoverPublisher->publish($event); + } + }; + } + + private function publishEvent(DomainEvent $event): void + { + $body = DomainEventJsonSerializer::serialize($event); + $routingKey = $event::eventName(); + $messageId = $event->eventId(); + + $this->connection->exchange($this->exchangeName)->publish( + $body, + $routingKey, + AMQP_NOPARAM, + [ + 'message_id' => $messageId, + 'content_type' => 'application/json', + 'content_encoding' => 'utf-8', + ] + ); + } } diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php index 9dcf6fd1e..7e9be1dc9 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php @@ -1,18 +1,18 @@ Utils::toSnakeCase($text); - } + private static function toSnakeCase(): callable + { + return static fn (string $text): string => Utils::toSnakeCase($text); + } } diff --git a/src/Shared/Infrastructure/Bus/Event/WithMonitoring/WithPrometheusMonitoringEventBus.php b/src/Shared/Infrastructure/Bus/Event/WithMonitoring/WithPrometheusMonitoringEventBus.php new file mode 100644 index 000000000..c94edfdcc --- /dev/null +++ b/src/Shared/Infrastructure/Bus/Event/WithMonitoring/WithPrometheusMonitoringEventBus.php @@ -0,0 +1,34 @@ +monitor->registry()->getOrRegisterCounter( + $this->appName, + 'domain_event', + 'Domain Events', + ['name'] + ); + + each(fn (DomainEvent $event) => $counter->inc(['name' => $event::eventName()]), $events); + + $this->bus->publish(...$events); + } +} diff --git a/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php b/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php index 781418a66..4812214d0 100644 --- a/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php +++ b/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php @@ -1,6 +1,6 @@ bus = new MessageBus( - [ - new HandleMessageMiddleware( - new HandlersLocator(CallableFirstParameterExtractor::forCallables($queryHandlers)) - ), - ] - ); - } + public function __construct(iterable $queryHandlers) + { + $this->bus = new MessageBus( + [ + new HandleMessageMiddleware(new HandlersLocator(CallableFirstParameterExtractor::forCallables($queryHandlers))), + ] + ); + } - public function ask(Query $query): ?Response - { - try { - /** @var HandledStamp $stamp */ - $stamp = $this->bus->dispatch($query)->last(HandledStamp::class); + public function ask(Query $query): ?Response + { + try { + /** @var HandledStamp $stamp */ + $stamp = $this->bus->dispatch($query)->last(HandledStamp::class); - return $stamp->getResult(); - } catch (NoHandlerForMessageException $unused) { - throw new QueryNotRegisteredError($query); - } - } + return $stamp->getResult(); + } catch (NoHandlerForMessageException) { + throw new QueryNotRegisteredError($query); + } + } } diff --git a/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php b/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php index f098adac8..c79d4859c 100644 --- a/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php +++ b/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php @@ -1,6 +1,6 @@ hasn't a query handler associated"); - } + parent::__construct("The query <$queryClass> has no associated query handler"); + } } diff --git a/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php b/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php new file mode 100644 index 000000000..fdad67057 --- /dev/null +++ b/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php @@ -0,0 +1,12 @@ +connections = iterator_to_array($connections); - } - - public function clear(): void - { - each($this->clearer(), $this->connections); - } - - public function allConnectionsClearer(): callable - { - return function (): void { - $this->clear(); - }; - } - - public function truncate(): void - { - apply(new DatabaseCleaner(), array_values($this->connections)); - } - - private function clearer(): callable - { - return static function (EntityManager $entityManager) { - $entityManager->clear(); - }; - } + private readonly array $connections; + + public function __construct(iterable $connections) + { + $this->connections = Utils::iterableToArray($connections); + } + + public function clear(): void + { + each(fn (EntityManager $entityManager) => $entityManager->clear(), $this->connections); + } + + public function truncate(): void + { + apply(new MySqlDatabaseCleaner(), array_values($this->connections)); + } } diff --git a/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php b/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php index c2ca64491..e76a46c3b 100644 --- a/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php +++ b/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php @@ -1,29 +1,32 @@ 'CodelyTv\Shared\Domain', - ]; - - public static function create( - array $parameters, - array $contextPrefixes, - bool $isDevMode, - string $schemaFile, - array $dbalCustomTypesClasses - ): EntityManager { - if ($isDevMode) { - static::generateDatabaseIfNotExists($parameters, $schemaFile); - } - - DbalCustomTypesRegistrar::register($dbalCustomTypesClasses); - - return EntityManager::create($parameters, self::createConfiguration($contextPrefixes, $isDevMode)); - } - - private static function generateDatabaseIfNotExists(array $parameters, string $schemaFile): void - { - self::ensureSchemaFileExists($schemaFile); - - $databaseName = $parameters['dbname']; - $parametersWithoutDatabaseName = dissoc($parameters, 'dbname'); - $connection = DriverManager::getConnection($parametersWithoutDatabaseName); - $schemaManager = new MySqlSchemaManager($connection); - - if (!self::databaseExists($databaseName, $schemaManager)) { - $schemaManager->createDatabase($databaseName); - $connection->exec(sprintf('USE %s', $databaseName)); - $connection->exec(file_get_contents(realpath($schemaFile))); - } - - $connection->close(); - } - - private static function databaseExists($databaseName, MySqlSchemaManager $schemaManager): bool - { - return in_array($databaseName, $schemaManager->listDatabases(), true); - } - - private static function ensureSchemaFileExists(string $schemaFile): void - { - if (!file_exists($schemaFile)) { - throw new RuntimeException(sprintf('The file <%s> does not exist', $schemaFile)); - } - } - - private static function createConfiguration(array $contextPrefixes, bool $isDevMode): Configuration - { - $config = Setup::createConfiguration($isDevMode, null, new ArrayCache()); - - $config->setMetadataDriverImpl(new SimplifiedXmlDriver(array_merge(self::$sharedPrefixes, $contextPrefixes))); - - return $config; - } + private static array $sharedPrefixes = [ + __DIR__ . '/../../../Shared/Infrastructure/Persistence/Mappings' => 'CodelyTv\Shared\Domain', + ]; + + public static function create( + array $parameters, + array $contextPrefixes, + bool $isDevMode, + string $schemaFile, + array $dbalCustomTypesClasses + ): EntityManager { + if ($isDevMode) { + self::generateDatabaseIfNotExists($parameters, $schemaFile); + } + + DbalCustomTypesRegistrar::register($dbalCustomTypesClasses); + + $config = self::createConfiguration($contextPrefixes, $isDevMode); + $eventManager = new EventManager(); + + return new EntityManager( + DriverManager::getConnection($parameters, $config, $eventManager), + $config, + $eventManager + ); + } + + private static function generateDatabaseIfNotExists(array $parameters, string $schemaFile): void + { + self::ensureSchemaFileExists($schemaFile); + + $databaseName = $parameters['dbname']; + $parametersWithoutDatabaseName = dissoc($parameters, 'dbname'); + $connection = DriverManager::getConnection($parametersWithoutDatabaseName); + $platform = new MariaDBPlatform(); + $schemaManager = new MySQLSchemaManager($connection, $platform); + + if (!self::databaseExists($databaseName, $schemaManager)) { + $schemaManager->createDatabase($databaseName); + + $connection->executeStatement(sprintf('USE %s', $databaseName)); + $connection->executeStatement(file_get_contents(realpath($schemaFile))); + } + + $connection->close(); + } + + private static function databaseExists(string $databaseName, MySQLSchemaManager $schemaManager): bool + { + return in_array($databaseName, $schemaManager->listDatabases(), true); + } + + private static function ensureSchemaFileExists(string $schemaFile): void + { + if (!file_exists($schemaFile)) { + throw new RuntimeException(sprintf('The file <%s> does not exist', $schemaFile)); + } + } + + private static function createConfiguration(array $contextPrefixes, bool $isDevMode): Configuration + { + $config = ORMSetup::createConfiguration($isDevMode); + + $config->setMetadataDriverImpl(new SimplifiedXmlDriver(array_merge(self::$sharedPrefixes, $contextPrefixes))); + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + + return $config; + } } diff --git a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClient.php b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClient.php index a3bc2f81f..41480e009 100644 --- a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClient.php +++ b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClient.php @@ -1,40 +1,33 @@ client = $client; - $this->indexPrefix = $indexPrefix; - } - - public function persist(string $aggregateName, string $identifier, array $plainBody): void - { - $this->client->index( - [ - 'index' => sprintf('%s_%s', $this->indexPrefix, $aggregateName), - 'id' => $identifier, - 'body' => $plainBody, - ] - ); - } - - public function client(): Client - { - return $this->client; - } - - public function indexPrefix(): string - { - return $this->indexPrefix; - } + public function __construct(private Client $client, private string $indexPrefix) {} + + public function persist(string $aggregateName, string $identifier, array $plainBody): void + { + $this->client->index( + [ + 'index' => sprintf('%s_%s', $this->indexPrefix, $aggregateName), + 'id' => $identifier, + 'body' => $plainBody, + ] + ); + } + + public function client(): Client + { + return $this->client; + } + + public function indexPrefix(): string + { + return $this->indexPrefix; + } } diff --git a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClientFactory.php b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClientFactory.php index c3fd21f4b..a5472afb9 100644 --- a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClientFactory.php +++ b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClientFactory.php @@ -1,6 +1,6 @@ setHosts([$host])->build(); - - $this->generateIndexIfNotExists($client, $indexPrefix, $schemasFolder, $environment); - - return new ElasticsearchClient($client, $indexPrefix); - } - - private function generateIndexIfNotExists( - Client $client, - string $indexPrefix, - string $schemasFolder, - $environment - ): void { - $indexes = Utils::filesIn($schemasFolder, '.json'); - - foreach ($indexes as $index) { - $indexName = str_replace('.json', '', sprintf('%s_%s', $indexPrefix, $index)); - - if ('prod' !== $environment && !$this->indexExists($client, $indexName)) { - $indexBody = Utils::jsonDecode(file_get_contents("$schemasFolder/$index")); - - $client->indices()->create(['index' => $indexName, 'body' => $indexBody]); - } - } - } - - private function indexExists(Client $client, string $indexName): bool - { - try { - $client->indices()->getSettings(['index' => $indexName]); - - return true; - } catch (Missing404Exception $unused) { - return false; - } - } + public function __invoke( + string $host, + string $indexPrefix, + string $schemasFolder, + string $environment + ): ElasticsearchClient { + $client = ClientBuilder::create()->setHosts([$host])->build(); + + $this->generateIndexIfNotExists($client, $indexPrefix, $schemasFolder, $environment); + + return new ElasticsearchClient($client, $indexPrefix); + } + + private function generateIndexIfNotExists( + Client $client, + string $indexPrefix, + string $schemasFolder, + string $environment + ): void { + if ($environment !== 'prod') { + return; + } + + $indexes = Utils::filesIn($schemasFolder, '.json'); + + foreach ($indexes as $index) { + $indexName = str_replace('.json', '', sprintf('%s_%s', $indexPrefix, $index)); + + if (!$this->indexExists($client, $indexName)) { + $indexBody = Utils::jsonDecode(file_get_contents("$schemasFolder/$index")); + + $client->indices()->create(['index' => $indexName, 'body' => $indexBody]); + } + } + } + + private function indexExists(Client $client, string $indexName): bool + { + try { + $client->indices()->getSettings(['index' => $indexName]); + + return true; + } catch (Missing404Exception) { + return false; + } + } } diff --git a/src/Shared/Infrastructure/Logger/MonologLogger.php b/src/Shared/Infrastructure/Logger/MonologLogger.php index 4c06a0c6a..1e0928028 100644 --- a/src/Shared/Infrastructure/Logger/MonologLogger.php +++ b/src/Shared/Infrastructure/Logger/MonologLogger.php @@ -1,32 +1,27 @@ logger = $logger; - } - - public function info(string $message, array $context = []): void - { - $this->logger->info($message, $context); - } - - public function warning(string $message, array $context = []): void - { - $this->logger->warning($message, $context); - } - - public function critical(string $message, array $context = []): void - { - $this->logger->critical($message, $context); - } + public function __construct(private \Monolog\Logger $logger) {} + + public function info(string $message, array $context = []): void + { + $this->logger->info($message, $context); + } + + public function warning(string $message, array $context = []): void + { + $this->logger->warning($message, $context); + } + + public function critical(string $message, array $context = []): void + { + $this->logger->critical($message, $context); + } } diff --git a/src/Shared/Infrastructure/Monitoring/PrometheusMonitor.php b/src/Shared/Infrastructure/Monitoring/PrometheusMonitor.php new file mode 100644 index 000000000..162c0482e --- /dev/null +++ b/src/Shared/Infrastructure/Monitoring/PrometheusMonitor.php @@ -0,0 +1,23 @@ +registry = new CollectorRegistry(new APC()); + } + + public function registry(): CollectorRegistry + { + return $this->registry; + } +} diff --git a/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineCriteriaConverter.php b/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineCriteriaConverter.php index d5a96b719..88bd57083 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineCriteriaConverter.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineCriteriaConverter.php @@ -1,6 +1,6 @@ criteria = $criteria; - $this->criteriaToDoctrineFields = $criteriaToDoctrineFields; - $this->hydrators = $hydrators; - } - - public static function convert( - Criteria $criteria, - array $criteriaToDoctrineFields = [], - array $hydrators = [] - ): DoctrineCriteria { - $converter = new self($criteria, $criteriaToDoctrineFields, $hydrators); - - return $converter->convertToDoctrineCriteria(); - } - - public static function convertToCount( - Criteria $criteria, - array $criteriaToDoctrineFields = [], - array $hydrators = [] - ): DoctrineCriteria { - $converter = new self($criteria, $criteriaToDoctrineFields, $hydrators); - - return $converter->convertToDoctrineCriteriaToCount(); - } - - private function convertToDoctrineCriteria(): DoctrineCriteria - { - return new DoctrineCriteria( - $this->buildExpression($this->criteria), - $this->formatOrder($this->criteria), - $this->criteria->offset(), - $this->criteria->limit() - ); - } - - private function convertToDoctrineCriteriaToCount(): DoctrineCriteria - { - return new DoctrineCriteria($this->buildExpression($this->criteria), $this->formatOrder($this->criteria)); - } - - private function buildExpression(Criteria $criteria): ?CompositeExpression - { - if ($criteria->hasFilters()) { - return new CompositeExpression( - CompositeExpression::TYPE_AND, - array_map($this->buildComparison(), $criteria->plainFilters()) - ); - } - - return null; - } - - private function buildComparison(): callable - { - return function (Filter $filter): Comparison { - $field = $this->mapFieldValue($filter->field()); - $value = $this->existsHydratorFor($field) - ? $this->hydrate($field, $filter->value()->value()) - : $filter->value()->value(); - - return new Comparison($field, $filter->operator()->value(), $value); - }; - } - - private function mapFieldValue(FilterField $field) - { - return array_key_exists($field->value(), $this->criteriaToDoctrineFields) - ? $this->criteriaToDoctrineFields[$field->value()] - : $field->value(); - } - - private function formatOrder(Criteria $criteria): ?array - { - if (!$criteria->hasOrder()) { - return null; - } - - return [$this->mapOrderBy($criteria->order()->orderBy()) => $criteria->order()->orderType()]; - } - - private function mapOrderBy(OrderBy $field) - { - return array_key_exists($field->value(), $this->criteriaToDoctrineFields) - ? $this->criteriaToDoctrineFields[$field->value()] - : $field->value(); - } - - private function existsHydratorFor($field): bool - { - return array_key_exists($field, $this->hydrators); - } - - private function hydrate($field, $value) - { - return $this->hydrators[$field]($value); - } + public function __construct( + private Criteria $criteria, + private array $criteriaToDoctrineFields = [], + private array $hydrators = [] + ) {} + + public static function convert( + Criteria $criteria, + array $criteriaToDoctrineFields = [], + array $hydrators = [] + ): DoctrineCriteria { + $converter = new self($criteria, $criteriaToDoctrineFields, $hydrators); + + return $converter->convertToDoctrineCriteria(); + } + + private function convertToDoctrineCriteria(): DoctrineCriteria + { + return new DoctrineCriteria( + $this->buildExpression($this->criteria), + $this->formatOrder($this->criteria), + $this->criteria->offset(), + $this->criteria->limit() + ); + } + + private function buildExpression(Criteria $criteria): ?CompositeExpression + { + if ($criteria->hasFilters()) { + return new CompositeExpression( + CompositeExpression::TYPE_AND, + array_map($this->buildComparison(), $criteria->plainFilters()) + ); + } + + return null; + } + + private function buildComparison(): callable + { + return function (Filter $filter): Comparison { + $field = $this->mapFieldValue($filter->field()); + $value = $this->existsHydratorFor($field) + ? $this->hydrate($field, $filter->value()->value()) + : $filter->value()->value(); + + return new Comparison($field, $filter->operator()->value, $value); + }; + } + + private function mapFieldValue(FilterField $field): mixed + { + return array_key_exists($field->value(), $this->criteriaToDoctrineFields) + ? $this->criteriaToDoctrineFields[$field->value()] + : $field->value(); + } + + private function formatOrder(Criteria $criteria): ?array + { + if (!$criteria->hasOrder()) { + return null; + } + + return [$this->mapOrderBy($criteria->order()->orderBy()) => $criteria->order()->orderType()]; + } + + private function mapOrderBy(OrderBy $field): mixed + { + return array_key_exists($field->value(), $this->criteriaToDoctrineFields) + ? $this->criteriaToDoctrineFields[$field->value()] + : $field->value(); + } + + private function existsHydratorFor(mixed $field): bool + { + return array_key_exists($field, $this->hydrators); + } + + private function hydrate(mixed $field, string $value): mixed + { + return $this->hydrators[$field]($value); + } } diff --git a/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php b/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php index 02721e73c..dc6cdd9bb 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php @@ -1,41 +1,46 @@ entityManager = $entityManager; - } - - protected function entityManager(): EntityManager - { - return $this->entityManager; - } - - protected function persist(AggregateRoot $entity): void - { - $this->entityManager()->persist($entity); - $this->entityManager()->flush($entity); - } - - protected function remove(AggregateRoot $entity): void - { - $this->entityManager()->remove($entity); - $this->entityManager()->flush($entity); - } - - protected function repository($entityClass): EntityRepository - { - return $this->entityManager->getRepository($entityClass); - } + public function __construct(private readonly EntityManager $entityManager) {} + + protected function entityManager(): EntityManager + { + return $this->entityManager; + } + + protected function persist(AggregateRoot $entity): void + { + $this->entityManager()->persist($entity); + $this->entityManager()->flush($entity); + } + + protected function remove(AggregateRoot $entity): void + { + $this->entityManager()->remove($entity); + $this->entityManager()->flush($entity); + } + + /** + * @template T of object + * + * @psalm-param class-string $entityClass + * + * @psalm-return EntityRepository + * + * @throws NotSupported + */ + protected function repository(string $entityClass): EntityRepository + { + return $this->entityManager->getRepository($entityClass); + } } diff --git a/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php b/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php index 4d3e9986e..1ef8213bc 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php @@ -1,33 +1,41 @@ typeClassName(); - - return new $className($value); - } - - /** @var Uuid $value */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) - { - return $value->value(); - } + abstract protected function typeClassName(): string; + + final public static function customTypeName(): string + { + return Utils::toSnakeCase(str_replace('Type', '', (string) last(explode('\\', static::class)))); + } + + final public function getName(): string + { + return self::customTypeName(); + } + + final public function convertToPHPValue($value, AbstractPlatform $platform): mixed + { + $className = $this->typeClassName(); + + return new $className($value); + } + + final public function convertToDatabaseValue($value, AbstractPlatform $platform): string + { + /** @var Uuid $value */ + return $value->value(); + } } diff --git a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticQueryGenerator.php b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticQueryGenerator.php index fdcf26a4f..885066f5e 100644 --- a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticQueryGenerator.php +++ b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticQueryGenerator.php @@ -1,58 +1,52 @@ self::TERM_TERM, - FilterOperator::NOT_EQUAL => '!=', - FilterOperator::GT => self::TERM_RANGE, - FilterOperator::LT => self::TERM_RANGE, - FilterOperator::CONTAINS => self::TERM_WILDCARD, - FilterOperator::NOT_CONTAINS => self::TERM_WILDCARD, - ]; - private static array $mustNotFields = [FilterOperator::NOT_EQUAL, FilterOperator::NOT_CONTAINS]; - - public function __invoke(array $query, Filter $filter): array - { - $type = $this->typeFor($filter->operator()); - $termLevel = $this->termLevelFor($filter->operator()); - $valueTemplate = $filter->operator()->isContaining() ? '*%s*' : '%s'; - - return array_merge_recursive( - $query, - [ - $type => [ - $termLevel => [ - $filter->field()->value() => sprintf( - $valueTemplate, - strtolower($filter->value()->value()) - ), - ], - ], - ] - ); - } - - private function typeFor(FilterOperator $operator): string - { - return in_array($operator->value(), self::$mustNotFields, true) ? self::MUST_NOT_TYPE : self::MUST_TYPE; - } - - private function termLevelFor(FilterOperator $operator): string - { - return get($operator->value(), self::$termMapping); - } + private const MUST_TYPE = 'must'; + private const MUST_NOT_TYPE = 'must_not'; + private const TERM_TERM = 'term'; + private const TERM_RANGE = 'range'; + private const TERM_WILDCARD = 'wildcard'; + + private static array $mustNotFields = [FilterOperator::NOT_EQUAL, FilterOperator::NOT_CONTAINS]; + + public function __invoke(array $query, Filter $filter): array + { + $type = $this->typeFor($filter->operator()); + $termLevel = $this->termLevelFor($filter->operator()); + $valueTemplate = $filter->operator()->isContaining() ? '*%s*' : '%s'; + + return array_merge_recursive( + $query, + [ + $type => [ + $termLevel => [ + $filter->field()->value() => sprintf($valueTemplate, strtolower($filter->value()->value())), + ], + ], + ] + ); + } + + private function typeFor(FilterOperator $operator): string + { + return in_array($operator->value, self::$mustNotFields, true) ? self::MUST_NOT_TYPE : self::MUST_TYPE; + } + + private function termLevelFor(FilterOperator $operator): string + { + return match ($operator) { + FilterOperator::EQUAL => self::TERM_TERM, + FilterOperator::NOT_EQUAL => '!=', + FilterOperator::GT, FilterOperator::LT => self::TERM_RANGE, + FilterOperator::CONTAINS, FilterOperator::NOT_CONTAINS => self::TERM_WILDCARD, + }; + } } diff --git a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php index 85c95d5af..257e531b3 100644 --- a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php +++ b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php @@ -1,48 +1,53 @@ array_merge( - ['from' => $criteria->offset() ?: 0, 'size' => $criteria->limit() ?: 1000], - $this->formatQuery($criteria), - $this->formatSort($criteria) - ), - ]; - } - - private function formatQuery(Criteria $criteria): array - { - if ($criteria->hasFilters()) { - $multipleFilters = 1 < $criteria->filters()->count(); - - return [ - 'query' => [ - 'bool' => reduce(new ElasticQueryGenerator(), $criteria->filters(), []), - ], - ]; - } - - return []; - } - - private function formatSort(Criteria $criteria): array - { - if ($criteria->hasOrder()) { - $order = $criteria->order(); - - return ['sort' => [$order->orderBy()->value() => ['order' => $order->orderType()->value()]]]; - } - - return []; - } + public function convert(Criteria $criteria): array + { + return [ + 'body' => array_merge( + ['from' => $criteria->offset() ?: 0, 'size' => $criteria->limit() ?: 1000], + $this->formatQuery($criteria), + $this->formatSort($criteria) + ), + ]; + } + + private function formatQuery(Criteria $criteria): array + { + if ($criteria->hasFilters()) { + return [ + 'query' => [ + 'bool' => reduce(new ElasticQueryGenerator(), $criteria->filters(), []), + ], + ]; + } + + return []; + } + + private function formatSort(Criteria $criteria): array + { + if ($criteria->hasOrder()) { + $order = $criteria->order(); + + return [ + 'sort' => [ + $order->orderBy()->value() => [ + 'order' => $order->orderType()->value, + ], + ], + ]; + } + + return []; + } } diff --git a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchRepository.php b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchRepository.php index fac27de30..3236dd248 100644 --- a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchRepository.php +++ b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchRepository.php @@ -1,65 +1,61 @@ client = $client; - } + public function __construct(private readonly ElasticsearchClient $client) {} - abstract protected function aggregateName(): string; + abstract protected function aggregateName(): string; - public function searchByCriteria(Criteria $criteria): array - { - $converter = new ElasticsearchCriteriaConverter(); + final public function searchByCriteria(Criteria $criteria): array + { + $converter = new ElasticsearchCriteriaConverter(); - $query = $converter->convert($criteria); + $query = $converter->convert($criteria); - return $this->searchRawElasticsearchQuery($query); - } + return $this->searchRawElasticsearchQuery($query); + } - protected function persist(string $id, array $plainBody): void - { - $this->client->persist($this->aggregateName(), $id, $plainBody); - } + protected function persist(string $id, array $plainBody): void + { + $this->client->persist($this->aggregateName(), $id, $plainBody); + } - protected function searchAllInElastic(): array - { - return $this->searchRawElasticsearchQuery([]); - } + protected function searchAllInElastic(): array + { + return $this->searchRawElasticsearchQuery([]); + } - protected function searchRawElasticsearchQuery(array $params): array - { - try { - $result = $this->client->client()->search(array_merge(['index' => $this->indexName()], $params)); + protected function searchRawElasticsearchQuery(array $params): array + { + try { + $result = $this->client->client()->search(array_merge(['index' => $this->indexName()], $params)); - $hits = get_in(['hits', 'hits'], $result, []); + $hits = (array) get_in(['hits', 'hits'], $result, []); - return map($this->elasticValuesExtractor(), $hits); - } catch (Missing404Exception $unused) { - return []; - } - } + return map($this->elasticValuesExtractor(), $hits); + } catch (Missing404Exception) { + return []; + } + } - protected function indexName(): string - { - return sprintf('%s_%s', $this->client->indexPrefix(), $this->aggregateName()); - } + protected function indexName(): string + { + return sprintf('%s_%s', $this->client->indexPrefix(), $this->aggregateName()); + } - private function elasticValuesExtractor(): callable - { - return static fn(array $elasticValues): array => $elasticValues['_source']; - } + private function elasticValuesExtractor(): callable + { + return static fn (array $elasticValues): array => $elasticValues['_source']; + } } diff --git a/src/Shared/Infrastructure/PhpRandomNumberGenerator.php b/src/Shared/Infrastructure/PhpRandomNumberGenerator.php index 630fd2a81..b32a8be34 100644 --- a/src/Shared/Infrastructure/PhpRandomNumberGenerator.php +++ b/src/Shared/Infrastructure/PhpRandomNumberGenerator.php @@ -1,6 +1,6 @@ toString(); - } + public function generate(): string + { + return Uuid::uuid4()->toString(); + } } diff --git a/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php b/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php index d0786f128..0930af5ee 100644 --- a/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php +++ b/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php @@ -1,6 +1,6 @@ getRequest(); - $requestContents = $request->getContent(); + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + $requestContents = $request->getContent(); - if (!empty($requestContents) && $this->containsHeader($request, 'Content-Type', 'application/json')) { - $jsonData = json_decode($requestContents, true); - if (!$jsonData) { - throw new HttpException(Response::HTTP_BAD_REQUEST, 'Invalid json data'); - } - $jsonDataLowerCase = []; - foreach ($jsonData as $key => $value) { - $jsonDataLowerCase[preg_replace_callback( - '/_(.)/', - static fn($matches) => strtoupper($matches[1]), - $key - )] = $value; - } - $request->request->replace($jsonDataLowerCase); - } - } + if (!empty($requestContents) && $this->containsHeader($request, 'Content-Type', 'application/json')) { + $jsonData = json_decode($requestContents, true, 512, JSON_THROW_ON_ERROR); + if (!$jsonData) { + throw new HttpException(Response::HTTP_BAD_REQUEST, 'Invalid json data'); + } + $jsonDataLowerCase = []; + foreach ($jsonData as $key => $value) { + $jsonDataLowerCase[preg_replace_callback( + '/_(.)/', + static fn ($matches): string => strtoupper((string) $matches[1]), + (string) $key + )] = $value; + } + $request->request->replace($jsonDataLowerCase); + } + } - private function containsHeader(Request $request, $name, $value): bool - { - return 0 === strpos($request->headers->get($name), $value); - } + private function containsHeader(Request $request, string $name, string $value): bool + { + return str_starts_with((string) $request->headers->get($name), $value); + } } diff --git a/src/Shared/Infrastructure/Symfony/ApiController.php b/src/Shared/Infrastructure/Symfony/ApiController.php index e254d0409..09dd9be51 100644 --- a/src/Shared/Infrastructure/Symfony/ApiController.php +++ b/src/Shared/Infrastructure/Symfony/ApiController.php @@ -1,6 +1,6 @@ queryBus = $queryBus; - $this->commandBus = $commandBus; - $this->exceptionHandler = $exceptionHandler; - - each($this->exceptionRegistrar(), $this->exceptions()); - } - - abstract protected function exceptions(): array; - - protected function ask(Query $query): ?Response - { - return $this->queryBus->ask($query); - } - - protected function dispatch(Command $command): void - { - $this->commandBus->dispatch($command); - } - - private function exceptionRegistrar(): callable - { - return function ($httpCode, $exception): void { - $this->exceptionHandler->register($exception, $httpCode); - }; - } + public function __construct( + private readonly QueryBus $queryBus, + private readonly CommandBus $commandBus, + ApiExceptionsHttpStatusCodeMapping $exceptionHandler + ) { + each( + fn (int $httpCode, string $exceptionClass) => $exceptionHandler->register($exceptionClass, $httpCode), + $this->exceptions() + ); + } + + abstract protected function exceptions(): array; + + protected function ask(Query $query): ?Response + { + return $this->queryBus->ask($query); + } + + protected function dispatch(Command $command): void + { + $this->commandBus->dispatch($command); + } } diff --git a/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php b/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php index d8d7aa8b0..d913d8766 100644 --- a/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php +++ b/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php @@ -1,43 +1,48 @@ exceptionHandler = $exceptionHandler; - } - - public function onException(RequestEvent $event): void - { - $exception = $event->getException(); - - $event->setResponse( - new JsonResponse( - [ - 'code' => $this->exceptionCodeFor($exception), - 'message' => $exception->getMessage(), - ], - $this->exceptionHandler->statusCodeFor(get_class($exception)) - ) - ); - } - - private function exceptionCodeFor(Exception $error): string - { - $domainErrorClass = DomainError::class; - - return $error instanceof $domainErrorClass ? $error->errorCode() : Utils::toSnakeCase(class_basename($error)); - } + public function __construct(private ApiExceptionsHttpStatusCodeMapping $exceptionHandler) {} + + public function onException(ExceptionEvent $event): void + { + $exception = $event->getThrowable(); + + $event->setResponse( + new JsonResponse( + [ + 'code' => $this->exceptionCodeFor($exception), + 'message' => $exception->getMessage(), + ], + $this->exceptionHandler->statusCodeFor($exception::class) + ) + ); + } + + private function exceptionCodeFor(Throwable $error): string + { + $domainErrorClass = DomainError::class; + + return $error instanceof $domainErrorClass + ? $error->errorCode() + : Utils::toSnakeCase($this->extractClassName($error)); + } + + private function extractClassName(object $object): string + { + $reflect = new ReflectionClass($object); + + return $reflect->getShortName(); + } } diff --git a/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php b/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php index c99249232..8b1ad8a11 100644 --- a/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php +++ b/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php @@ -1,34 +1,36 @@ Response::HTTP_BAD_REQUEST, - NotFoundHttpException::class => Response::HTTP_NOT_FOUND, - ]; - - public function register($exceptionClass, $statusCode): void - { - $this->exceptions[$exceptionClass] = $statusCode; - } - - public function exists($exceptionClass): bool - { - return array_key_exists($exceptionClass, $this->exceptions); - } - - public function statusCodeFor($exceptionClass): int - { - return get($exceptionClass, $this->exceptions, self::DEFAULT_STATUS_CODE); - } + private const DEFAULT_STATUS_CODE = Response::HTTP_INTERNAL_SERVER_ERROR; + private array $exceptions = [ + InvalidArgumentException::class => Response::HTTP_BAD_REQUEST, + NotFoundHttpException::class => Response::HTTP_NOT_FOUND, + ]; + + public function register(string $exceptionClass, int $statusCode): void + { + $this->exceptions[$exceptionClass] = $statusCode; + } + + public function statusCodeFor(string $exceptionClass): int + { + $statusCode = get($exceptionClass, $this->exceptions, self::DEFAULT_STATUS_CODE); + + if ($statusCode === null) { + throw new InvalidArgumentException("There are no status code mapping for <$exceptionClass>"); + } + + return $statusCode; + } } diff --git a/src/Shared/Infrastructure/Symfony/BasicHttpAuthMiddleware.php b/src/Shared/Infrastructure/Symfony/BasicHttpAuthMiddleware.php index 8973f9a5a..0f3ad3aa8 100644 --- a/src/Shared/Infrastructure/Symfony/BasicHttpAuthMiddleware.php +++ b/src/Shared/Infrastructure/Symfony/BasicHttpAuthMiddleware.php @@ -1,6 +1,6 @@ bus = $bus; - } - - public function onKernelRequest(RequestEvent $event): void - { - $shouldAuthenticate = $event->getRequest()->attributes->get('auth', false); - - if ($shouldAuthenticate) { - $user = $event->getRequest()->headers->get('php-auth-user'); - $pass = $event->getRequest()->headers->get('php-auth-pw'); - - $this->hasIntroducedCredentials($user) - ? $this->authenticate($user, $pass, $event) - : $this->askForCredentials($event); - } - } - - private function hasIntroducedCredentials(?string $user): bool - { - return null !== $user; - } - - private function authenticate(string $user, string $pass, RequestEvent $event): void - { - try { - $this->bus->dispatch(new AuthenticateUserCommand($user, $pass)); - - $this->addUserDataToRequest($user, $event); - } catch (InvalidAuthUsername | InvalidAuthCredentials $error) { - $event->setResponse(new JsonResponse(['error' => 'Invalid credentials'], Response::HTTP_FORBIDDEN)); - } - } - - private function addUserDataToRequest(string $user, RequestEvent $event): void - { - $event->getRequest()->attributes->set('authenticated_username', $user); - } - - private function askForCredentials(RequestEvent $event): void - { - $event->setResponse( - new Response('', Response::HTTP_UNAUTHORIZED, ['WWW-Authenticate' => 'Basic realm="CodelyTV"']) - ); - } + public function __construct(private CommandBus $bus) {} + + public function onKernelRequest(RequestEvent $event): void + { + $shouldAuthenticate = $event->getRequest()->attributes->get('auth', false); + + if ($shouldAuthenticate) { + $user = $event->getRequest()->headers->get('php-auth-user'); + $pass = $event->getRequest()->headers->get('php-auth-pw'); + + $this->hasIntroducedCredentials($user) + ? $this->authenticate($user, $pass, $event) + : $this->askForCredentials($event); + } + } + + private function hasIntroducedCredentials(?string $user): bool + { + return $user !== null; + } + + private function authenticate(string $user, string $pass, RequestEvent $event): void + { + try { + $this->bus->dispatch(new AuthenticateUserCommand($user, $pass)); + + $this->addUserDataToRequest($user, $event); + } catch (InvalidAuthCredentials | InvalidAuthUsername) { + $event->setResponse(new JsonResponse(['error' => 'Invalid credentials'], Response::HTTP_FORBIDDEN)); + } + } + + private function addUserDataToRequest(string $user, RequestEvent $event): void + { + $event->getRequest()->attributes->set('authenticated_username', $user); + } + + private function askForCredentials(RequestEvent $event): void + { + $event->setResponse( + new Response('', Response::HTTP_UNAUTHORIZED, [ + 'WWW-Authenticate' => 'Basic realm="CodelyTV"', + ]) + ); + } } diff --git a/src/Shared/Infrastructure/Symfony/FlashSession.php b/src/Shared/Infrastructure/Symfony/FlashSession.php index fe308c34d..61bc6a523 100644 --- a/src/Shared/Infrastructure/Symfony/FlashSession.php +++ b/src/Shared/Infrastructure/Symfony/FlashSession.php @@ -1,42 +1,42 @@ getFlashBag()->all()); - } - - public function get(string $key, $default = null) - { - if (array_key_exists($key, self::$flashes)) { - return self::$flashes[$key]; - } - - if (array_key_exists($key . '.0', self::$flashes)) { - return self::$flashes[$key . '.0']; - } - - if (array_key_exists($key . '.0.0', self::$flashes)) { - return self::$flashes[$key . '.0.0']; - } - - return $default; - } - - public function has(string $key): bool - { - return array_key_exists($key, self::$flashes) || - array_key_exists($key . '.0', self::$flashes) || - array_key_exists($key . '.0.0', self::$flashes); - } + private static array $flashes = []; + + public function __construct(RequestStack $requestStack) + { + self::$flashes = Utils::dot($requestStack->getSession()->getFlashBag()->all()); + } + + public function get(string $key, $default = null) + { + if (array_key_exists($key, self::$flashes)) { + return self::$flashes[$key]; + } + + if (array_key_exists($key . '.0', self::$flashes)) { + return self::$flashes[$key . '.0']; + } + + if (array_key_exists($key . '.0.0', self::$flashes)) { + return self::$flashes[$key . '.0.0']; + } + + return $default; + } + + public function has(string $key): bool + { + return array_key_exists($key, self::$flashes) + || array_key_exists($key . '.0', self::$flashes) + || array_key_exists($key . '.0.0', self::$flashes); + } } diff --git a/src/Shared/Infrastructure/Symfony/WebController.php b/src/Shared/Infrastructure/Symfony/WebController.php index bf9297372..741d764f0 100644 --- a/src/Shared/Infrastructure/Symfony/WebController.php +++ b/src/Shared/Infrastructure/Symfony/WebController.php @@ -1,6 +1,6 @@ twig->render($templatePath, $arguments)); + } - $this->twig = $twig; - $this->router = $router; - $this->session = $session; - } + final public function redirect(string $routeName): RedirectResponse + { + return new RedirectResponse($this->router->generate($routeName), 302); + } - public function render(string $templatePath, array $arguments = []): SymfonyResponse - { - return new SymfonyResponse($this->twig->render($templatePath, $arguments)); - } + final public function redirectWithMessage(string $routeName, string $message): RedirectResponse + { + $this->addFlashFor('message', [$message]); - public function redirect(string $routeName): RedirectResponse - { - return new RedirectResponse($this->router->generate($routeName), 302); - } + return $this->redirect($routeName); + } - public function redirectWithMessage(string $routeName, string $message): RedirectResponse - { - $this->addFlashFor('message', [$message]); + final public function redirectWithErrors( + string $routeName, + ConstraintViolationListInterface $errors, + Request $request + ): RedirectResponse { + $this->addFlashFor('errors', $this->formatFlashErrors($errors)); + $this->addFlashFor('inputs', $request->request->all()); - return $this->redirect($routeName); - } + return new RedirectResponse($this->router->generate($routeName), 302); + } - public function redirectWithErrors( - string $routeName, - ConstraintViolationListInterface $errors, - Request $request - ): RedirectResponse { - $this->addFlashFor('errors', $this->formatFlashErrors($errors)); - $this->addFlashFor('inputs', $request->request->all()); + private function formatFlashErrors(ConstraintViolationListInterface $violations): array + { + $errors = []; + foreach ($violations as $violation) { + $errors[str_replace(['[', ']'], ['', ''], $violation->getPropertyPath())] = $violation->getMessage(); + } - return new RedirectResponse($this->router->generate($routeName), 302); - } + return $errors; + } - private function formatFlashErrors(ConstraintViolationListInterface $violations): array - { - $errors = []; - foreach ($violations as $violation) { - $errors[str_replace(['[', ']'], ['', ''], $violation->getPropertyPath())] = $violation->getMessage(); - } - - return $errors; - } - - private function addFlashFor(string $prefix, array $messages): void - { - foreach ($messages as $key => $message) { - $this->session->getFlashBag()->set($prefix . '.' . $key, $message); - } - } + private function addFlashFor(string $prefix, array $messages): void + { + foreach ($messages as $key => $message) { + $this->requestStack->getSession()->getFlashBag()->set($prefix . '.' . $key, $message); + } + } } diff --git a/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php new file mode 100644 index 000000000..25787a7db --- /dev/null +++ b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php @@ -0,0 +1,62 @@ +handler = new AuthenticateUserCommandHandler(new UserAuthenticator($this->repository())); + } + + /** @test */ + public function it_should_authenticate_a_valid_user(): void + { + $command = AuthenticateUserCommandMother::create(); + $authUser = AuthUserMother::fromCommand($command); + + $this->shouldSearch($authUser->username(), $authUser); + + $this->dispatch($command, $this->handler); + } + + /** @test */ + public function it_should_throw_an_exception_when_the_user_does_not_exist(): void + { + $this->expectException(InvalidAuthUsername::class); + + $command = AuthenticateUserCommandMother::create(); + $username = AuthUsernameMother::create($command->username()); + + $this->shouldSearch($username); + + $this->dispatch($command, $this->handler); + } + + /** @test */ + public function it_should_throw_an_exception_when_the_password_does_not_math(): void + { + $this->expectException(InvalidAuthCredentials::class); + + $command = AuthenticateUserCommandMother::create(); + $authUser = AuthUserMother::create(username: AuthUsernameMother::create($command->username())); + + $this->shouldSearch($authUser->username(), $authUser); + + $this->dispatch($command, $this->handler); + } +} diff --git a/tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php similarity index 52% rename from tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php rename to tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php index 1e7156a8d..4418e2b9f 100644 --- a/tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php +++ b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php @@ -1,6 +1,6 @@ value(), $password->value()); - } - - public static function random(): AuthenticateUserCommand - { - return self::create(AuthUsernameMother::random(), AuthPasswordMother::random()); - } + public static function create( + ?AuthUsername $username = null, + ?AuthPassword $password = null + ): AuthenticateUserCommand { + return new AuthenticateUserCommand( + $username?->value() ?? AuthUsernameMother::create()->value(), + $password?->value() ?? AuthPasswordMother::create()->value() + ); + } } diff --git a/tests/Backoffice/Auth/AuthModuleUnitTestCase.php b/tests/Backoffice/Auth/AuthModuleUnitTestCase.php new file mode 100644 index 000000000..4a7ad4a7c --- /dev/null +++ b/tests/Backoffice/Auth/AuthModuleUnitTestCase.php @@ -0,0 +1,30 @@ +repository() + ->shouldReceive('search') + ->with($this->similarTo($username)) + ->once() + ->andReturn($authUser); + } + + protected function repository(): AuthRepository | MockInterface + { + return $this->repository ??= $this->mock(AuthRepository::class); + } +} diff --git a/tests/Backoffice/Auth/Domain/AuthPasswordMother.php b/tests/Backoffice/Auth/Domain/AuthPasswordMother.php new file mode 100644 index 000000000..9426c01c6 --- /dev/null +++ b/tests/Backoffice/Auth/Domain/AuthPasswordMother.php @@ -0,0 +1,16 @@ +username()), + AuthPasswordMother::create($command->password()) + ); + } +} diff --git a/tests/Backoffice/Auth/Domain/AuthUsernameMother.php b/tests/Backoffice/Auth/Domain/AuthUsernameMother.php new file mode 100644 index 000000000..988cedff5 --- /dev/null +++ b/tests/Backoffice/Auth/Domain/AuthUsernameMother.php @@ -0,0 +1,16 @@ +service(EntityManager::class)); + } + + protected function elasticRepository(): ElasticsearchBackofficeCourseRepository + { + return $this->service(ElasticsearchBackofficeCourseRepository::class); + } +} diff --git a/tests/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php b/tests/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php new file mode 100644 index 000000000..58e38d029 --- /dev/null +++ b/tests/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php @@ -0,0 +1,26 @@ + 'name', + 'operator' => 'CONTAINS', + 'value' => $text, + ]) + ) + ); + } +} diff --git a/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php b/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php new file mode 100644 index 000000000..9d2ffbe35 --- /dev/null +++ b/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php @@ -0,0 +1,22 @@ +value(), + $name ?? CourseNameMother::create()->value(), + $duration ?? CourseDurationMother::create()->value() + ); + } +} diff --git a/tests/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepositoryTest.php b/tests/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepositoryTest.php new file mode 100644 index 000000000..a45718666 --- /dev/null +++ b/tests/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepositoryTest.php @@ -0,0 +1,66 @@ +elasticRepository()->save(BackofficeCourseMother::create()); + } + + /** @test */ + public function it_should_search_all_existing_courses(): void + { + $existingCourse = BackofficeCourseMother::create(); + $anotherExistingCourse = BackofficeCourseMother::create(); + $existingCourses = [$existingCourse, $anotherExistingCourse]; + + $this->elasticRepository()->save($existingCourse); + $this->elasticRepository()->save($anotherExistingCourse); + + $this->eventually(fn () => $this->assertSimilar($existingCourses, $this->elasticRepository()->searchAll())); + } + + /** @test */ + public function it_should_search_all_existing_courses_with_an_empty_criteria(): void + { + $existingCourse = BackofficeCourseMother::create(); + $anotherExistingCourse = BackofficeCourseMother::create(); + $existingCourses = [$existingCourse, $anotherExistingCourse]; + + $this->elasticRepository()->save($existingCourse); + $this->elasticRepository()->save($anotherExistingCourse); + + $this->eventually( + fn () => $this->assertSimilar($existingCourses, $this->elasticRepository()->matching(CriteriaMother::empty())) + ); + } + + /** @test */ + public function it_should_filter_by_criteria(): void + { + $dddInPhpCourse = BackofficeCourseMother::create(name: 'DDD en PHP'); + $dddInJavaCourse = BackofficeCourseMother::create(name: 'DDD en Java'); + $intellijCourse = BackofficeCourseMother::create(name: 'Exprimiendo Intellij'); + $dddCourses = [$dddInPhpCourse, $dddInJavaCourse]; + + $nameContainsDddCriteria = BackofficeCourseCriteriaMother::nameContains('DDD'); + + $this->elasticRepository()->save($dddInJavaCourse); + $this->elasticRepository()->save($dddInPhpCourse); + $this->elasticRepository()->save($intellijCourse); + + $this->eventually( + fn () => $this->assertSimilar($dddCourses, $this->elasticRepository()->matching($nameContainsDddCriteria)) + ); + } +} diff --git a/tests/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php b/tests/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php new file mode 100644 index 000000000..300d01d99 --- /dev/null +++ b/tests/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php @@ -0,0 +1,64 @@ +mySqlRepository()->save(BackofficeCourseMother::create()); + } + + /** @test */ + public function it_should_search_all_existing_courses(): void + { + $existingCourse = BackofficeCourseMother::create(); + $anotherExistingCourse = BackofficeCourseMother::create(); + $existingCourses = [$existingCourse, $anotherExistingCourse]; + + $this->mySqlRepository()->save($existingCourse); + $this->mySqlRepository()->save($anotherExistingCourse); + + $this->assertSimilar($existingCourses, $this->mySqlRepository()->searchAll()); + } + + /** @test */ + public function it_should_search_all_existing_courses_with_an_empty_criteria(): void + { + $existingCourse = BackofficeCourseMother::create(); + $anotherExistingCourse = BackofficeCourseMother::create(); + $existingCourses = [$existingCourse, $anotherExistingCourse]; + + $this->mySqlRepository()->save($existingCourse); + $this->mySqlRepository()->save($anotherExistingCourse); + $this->clearUnitOfWork(); + + $this->assertSimilar($existingCourses, $this->mySqlRepository()->matching(CriteriaMother::empty())); + } + + /** @test */ + public function it_should_filter_by_criteria(): void + { + $dddInPhpCourse = BackofficeCourseMother::create(name: 'DDD en PHP'); + $dddInJavaCourse = BackofficeCourseMother::create(name: 'DDD en Java'); + $intellijCourse = BackofficeCourseMother::create(name: 'Exprimiendo Intellij'); + $dddCourses = [$dddInPhpCourse, $dddInJavaCourse]; + + $nameContainsDddCriteria = BackofficeCourseCriteriaMother::nameContains('DDD'); + + $this->mySqlRepository()->save($dddInJavaCourse); + $this->mySqlRepository()->save($dddInPhpCourse); + $this->mySqlRepository()->save($intellijCourse); + $this->clearUnitOfWork(); + + $this->assertSimilar($dddCourses, $this->mySqlRepository()->matching($nameContainsDddCriteria)); + } +} diff --git a/tests/Backoffice/Shared/Infraestructure/PhpUnit/BackofficeContextInfrastructureTestCase.php b/tests/Backoffice/Shared/Infraestructure/PhpUnit/BackofficeContextInfrastructureTestCase.php new file mode 100644 index 000000000..0292a6623 --- /dev/null +++ b/tests/Backoffice/Shared/Infraestructure/PhpUnit/BackofficeContextInfrastructureTestCase.php @@ -0,0 +1,30 @@ +service(ElasticsearchClient::class), + $this->service(EntityManager::class) + ); + + $arranger->arrange(); + } + + protected function kernelClass(): string + { + return BackofficeBackendKernel::class; + } +} diff --git a/tests/Backoffice/Shared/Infraestructure/PhpUnit/BackofficeEnvironmentArranger.php b/tests/Backoffice/Shared/Infraestructure/PhpUnit/BackofficeEnvironmentArranger.php new file mode 100644 index 000000000..33a33ea05 --- /dev/null +++ b/tests/Backoffice/Shared/Infraestructure/PhpUnit/BackofficeEnvironmentArranger.php @@ -0,0 +1,26 @@ +elasticsearchClient]); + apply(new MySqlDatabaseCleaner(), [$this->entityManager]); + } + + public function close(): void {} +} diff --git a/tests/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php b/tests/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php new file mode 100644 index 000000000..4fe82e139 --- /dev/null +++ b/tests/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php @@ -0,0 +1,37 @@ +handler = new CreateCourseCommandHandler(new CourseCreator($this->repository(), $this->eventBus())); + } + + /** @test */ + public function it_should_create_a_valid_course(): void + { + $command = CreateCourseCommandMother::create(); + + $course = CourseMother::fromRequest($command); + $domainEvent = CourseCreatedDomainEventMother::fromCourse($course); + + $this->shouldSave($course); + $this->shouldPublishDomainEvent($domainEvent); + + $this->dispatch($command, $this->handler); + } +} diff --git a/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php b/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php new file mode 100644 index 000000000..9d4b31fea --- /dev/null +++ b/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php @@ -0,0 +1,28 @@ +value() ?? CourseIdMother::create()->value(), + $name?->value() ?? CourseNameMother::create()->value(), + $duration?->value() ?? CourseDurationMother::create()->value() + ); + } +} diff --git a/tests/Mooc/Courses/Application/Update/CourseRenamerTest.php b/tests/Mooc/Courses/Application/Update/CourseRenamerTest.php new file mode 100644 index 000000000..7890b36f7 --- /dev/null +++ b/tests/Mooc/Courses/Application/Update/CourseRenamerTest.php @@ -0,0 +1,51 @@ +renamer = new CourseRenamer($this->repository(), $this->eventBus()); + } + + /** @test */ + public function it_should_rename_an_existing_course(): void + { + $course = CourseMother::create(); + $newName = CourseNameMother::create(); + $renamedCourse = DuplicatorMother::with($course, ['name' => $newName]); + + $this->shouldSearch($course->id(), $course); + $this->shouldSave($renamedCourse); + $this->shouldNotPublishDomainEvent(); + + $this->renamer->__invoke($course->id(), $newName); + } + + /** @test */ + public function it_should_throw_an_exception_when_the_course_not_exist(): void + { + $this->expectException(CourseNotExist::class); + + $id = CourseIdMother::create(); + + $this->shouldSearch($id, null); + + $this->renamer->__invoke($id, CourseNameMother::create()); + } +} diff --git a/tests/src/Mooc/Courses/CoursesModuleInfrastructureTestCase.php b/tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php similarity index 65% rename from tests/src/Mooc/Courses/CoursesModuleInfrastructureTestCase.php rename to tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php index 2d16a788a..67ad24223 100644 --- a/tests/src/Mooc/Courses/CoursesModuleInfrastructureTestCase.php +++ b/tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php @@ -1,6 +1,6 @@ service(CourseRepository::class); - } + protected function repository(): CourseRepository + { + return $this->service(CourseRepository::class); + } } diff --git a/tests/Mooc/Courses/CoursesModuleUnitTestCase.php b/tests/Mooc/Courses/CoursesModuleUnitTestCase.php new file mode 100644 index 000000000..b4d269546 --- /dev/null +++ b/tests/Mooc/Courses/CoursesModuleUnitTestCase.php @@ -0,0 +1,39 @@ +repository() + ->shouldReceive('save') + ->with($this->similarTo($course)) + ->once() + ->andReturnNull(); + } + + protected function shouldSearch(CourseId $id, ?Course $course): void + { + $this->repository() + ->shouldReceive('search') + ->with($this->similarTo($id)) + ->once() + ->andReturn($course); + } + + protected function repository(): CourseRepository | MockInterface + { + return $this->repository ??= $this->mock(CourseRepository::class); + } +} diff --git a/tests/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php b/tests/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php new file mode 100644 index 000000000..b04f7109d --- /dev/null +++ b/tests/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php @@ -0,0 +1,31 @@ +value() ?? CourseIdMother::create()->value(), + $name?->value() ?? CourseNameMother::create()->value(), + $duration?->value() ?? CourseDurationMother::create()->value() + ); + } + + public static function fromCourse(Course $course): CourseCreatedDomainEvent + { + return self::create($course->id(), $course->name(), $course->duration()); + } +} diff --git a/tests/Mooc/Courses/Domain/CourseDurationMother.php b/tests/Mooc/Courses/Domain/CourseDurationMother.php new file mode 100644 index 000000000..2cfc68c06 --- /dev/null +++ b/tests/Mooc/Courses/Domain/CourseDurationMother.php @@ -0,0 +1,26 @@ +id()), + CourseNameMother::create($request->name()), + CourseDurationMother::create($request->duration()) + ); + } +} diff --git a/tests/Mooc/Courses/Domain/CourseNameMother.php b/tests/Mooc/Courses/Domain/CourseNameMother.php new file mode 100644 index 000000000..72c4e7e02 --- /dev/null +++ b/tests/Mooc/Courses/Domain/CourseNameMother.php @@ -0,0 +1,16 @@ +repository()->save($course); + } + + /** @test */ + public function it_should_return_an_existing_course(): void + { + $course = CourseMother::create(); + + $this->repository()->save($course); + + $this->assertEquals($course, $this->repository()->search($course->id())); + } + + /** @test */ + public function it_should_not_return_a_non_existing_course(): void + { + $this->assertNull($this->repository()->search(CourseIdMother::create())); + } +} diff --git a/tests/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php b/tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php similarity index 50% rename from tests/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php rename to tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php index c95c58966..07a588148 100644 --- a/tests/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php +++ b/tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php @@ -1,6 +1,6 @@ value()); - } - - public static function random(): CoursesCounterResponse - { - return self::create(CoursesCounterTotalMother::random()); - } + public static function create(?CoursesCounterTotal $total = null): CoursesCounterResponse + { + return new CoursesCounterResponse($total?->value() ?? CoursesCounterTotalMother::create()->value()); + } } diff --git a/tests/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php b/tests/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php new file mode 100644 index 000000000..2c8bc2ef5 --- /dev/null +++ b/tests/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php @@ -0,0 +1,46 @@ +handler = new FindCoursesCounterQueryHandler(new CoursesCounterFinder($this->repository())); + } + + /** @test */ + public function it_should_find_an_existing_courses_counter(): void + { + $counter = CoursesCounterMother::create(); + $query = new FindCoursesCounterQuery(); + $response = CoursesCounterResponseMother::create($counter->total()); + + $this->shouldSearch($counter); + + $this->assertAskResponse($response, $query, $this->handler); + } + + /** @test */ + public function it_should_throw_an_exception_when_courses_counter_does_not_exists(): void + { + $query = new FindCoursesCounterQuery(); + + $this->shouldSearch(null); + + $this->assertAskThrowsException(CoursesCounterNotExist::class, $query, $this->handler); + } +} diff --git a/tests/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php b/tests/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php new file mode 100644 index 000000000..b81642fc2 --- /dev/null +++ b/tests/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php @@ -0,0 +1,74 @@ +subscriber = new IncrementCoursesCounterOnCourseCreated( + new CoursesCounterIncrementer($this->repository(), $this->uuidGenerator(), $this->eventBus()) + ); + } + + /** @test */ + public function it_should_initialize_a_new_counter(): void + { + $event = CourseCreatedDomainEventMother::create(); + + $courseId = CourseIdMother::create($event->aggregateId()); + $newCounter = CoursesCounterMother::withOne($courseId); + $domainEvent = CoursesCounterIncrementedDomainEventMother::fromCounter($newCounter); + + $this->shouldSearch(null); + $this->shouldGenerateUuid($newCounter->id()->value()); + $this->shouldSave($newCounter); + $this->shouldPublishDomainEvent($domainEvent); + + $this->notify($event, $this->subscriber); + } + + /** @test */ + public function it_should_increment_an_existing_counter(): void + { + $event = CourseCreatedDomainEventMother::create(); + + $courseId = CourseIdMother::create($event->aggregateId()); + $existingCounter = CoursesCounterMother::create(); + $incrementedCounter = CoursesCounterMother::incrementing($existingCounter, $courseId); + $domainEvent = CoursesCounterIncrementedDomainEventMother::fromCounter($incrementedCounter); + + $this->shouldSearch($existingCounter); + $this->shouldSave($incrementedCounter); + $this->shouldPublishDomainEvent($domainEvent); + + $this->notify($event, $this->subscriber); + } + + /** @test */ + public function it_should_not_increment_an_already_incremented_course(): void + { + $event = CourseCreatedDomainEventMother::create(); + + $courseId = CourseIdMother::create($event->aggregateId()); + $existingCounter = CoursesCounterMother::withOne($courseId); + + $this->shouldSearch($existingCounter); + + $this->notify($event, $this->subscriber); + } +} diff --git a/tests/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php b/tests/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php new file mode 100644 index 000000000..6937e3902 --- /dev/null +++ b/tests/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php @@ -0,0 +1,37 @@ +repository() + ->shouldReceive('save') + ->once() + ->with($this->similarTo($course)) + ->andReturnNull(); + } + + protected function shouldSearch(?CoursesCounter $counter): void + { + $this->repository() + ->shouldReceive('search') + ->once() + ->andReturn($counter); + } + + protected function repository(): CoursesCounterRepository | MockInterface + { + return $this->repository ??= $this->mock(CoursesCounterRepository::class); + } +} diff --git a/tests/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php b/tests/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php new file mode 100644 index 000000000..98bf659a1 --- /dev/null +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php @@ -0,0 +1,16 @@ +value() ?? CoursesCounterIdMother::create()->value(), + $total?->value() ?? CoursesCounterTotalMother::create()->value() + ); + } + + public static function fromCounter(CoursesCounter $counter): CoursesCounterIncrementedDomainEvent + { + return self::create($counter->id(), $counter->total()); + } +} diff --git a/tests/Mooc/CoursesCounter/Domain/CoursesCounterMother.php b/tests/Mooc/CoursesCounter/Domain/CoursesCounterMother.php new file mode 100644 index 000000000..d5200a5cd --- /dev/null +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterMother.php @@ -0,0 +1,41 @@ + CourseIdMother::create()) + ); + } + + public static function withOne(CourseId $courseId): CoursesCounter + { + return self::create(CoursesCounterIdMother::create(), CoursesCounterTotalMother::one(), $courseId); + } + + public static function incrementing(CoursesCounter $existingCounter, CourseId $courseId): CoursesCounter + { + return self::create( + $existingCounter->id(), + CoursesCounterTotalMother::create($existingCounter->total()->value() + 1), + ...array_merge($existingCounter->existingCourses(), [$courseId]) + ); + } +} diff --git a/tests/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php b/tests/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php new file mode 100644 index 000000000..0c29d090a --- /dev/null +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php @@ -0,0 +1,26 @@ +classes(Selector::inNamespace('/^CodelyTv\\\\Mooc\\\\.+\\\\Domain/', true)) + ->canOnlyDependOn() + ->classes(...array_merge(ArchitectureTest::languageClasses(), [ + // Itself + Selector::inNamespace('/^CodelyTv\\\\Mooc\\\\.+\\\\Domain/', true), + // Shared + Selector::inNamespace('CodelyTv\Shared\Domain'), + ])) + ->because('mooc domain can only import itself and shared domain'); + } + + public function test_mooc_application_should_only_import_itself_and_domain(): Rule + { + return PHPat::rule() + ->classes(Selector::inNamespace('/^CodelyTv\\\\Mooc\\\\.+\\\\Application/', true)) + ->canOnlyDependOn() + ->classes(...array_merge(ArchitectureTest::languageClasses(), [ + // Itself + Selector::inNamespace('/^CodelyTv\\\\Mooc\\\\.+\\\\Application/', true), + Selector::inNamespace('/^CodelyTv\\\\Mooc\\\\.+\\\\Domain/', true), + // Shared + Selector::inNamespace('CodelyTv\Shared'), + ])) + ->because('mooc application can only import itself and shared'); + } + + public function test_mooc_infrastructure_should_not_import_other_contexts_beside_shared(): Rule + { + return PHPat::rule() + ->classes(Selector::inNamespace('CodelyTv\Mooc')) + ->shouldNotDependOn() + ->classes(Selector::inNamespace('CodelyTv')) + ->excluding( + // Itself + Selector::inNamespace('CodelyTv\Mooc'), + // Shared + Selector::inNamespace('CodelyTv\Shared'), + ); + } +} diff --git a/tests/apps/backoffice/frontend/.gitkeep b/tests/Mooc/Shared/Domain/.gitkeep similarity index 100% rename from tests/apps/backoffice/frontend/.gitkeep rename to tests/Mooc/Shared/Domain/.gitkeep diff --git a/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php new file mode 100644 index 000000000..fb4bef7bf --- /dev/null +++ b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php @@ -0,0 +1,35 @@ +service(EntityManager::class)); + + $arranger->arrange(); + } + + protected function tearDown(): void + { + $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); + + $arranger->close(); + + parent::tearDown(); + } + + protected function kernelClass(): string + { + return MoocBackendKernel::class; + } +} diff --git a/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php new file mode 100644 index 000000000..39ddf5ef5 --- /dev/null +++ b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php @@ -0,0 +1,23 @@ +entityManager]); + } + + public function close(): void {} +} diff --git a/tests/Mooc/Steps/Domain/Exercise/ExerciseStepContentMother.php b/tests/Mooc/Steps/Domain/Exercise/ExerciseStepContentMother.php new file mode 100644 index 000000000..eac56c22f --- /dev/null +++ b/tests/Mooc/Steps/Domain/Exercise/ExerciseStepContentMother.php @@ -0,0 +1,16 @@ + QuizStepQuestionMother::create() + ) : $questions; + + return new QuizStep( + $id ?? StepIdMother::create(), + $title ?? StepTitleMother::create(), + $duration ?? StepDurationMother::create(), + ...$stepQuestions + ); + } +} diff --git a/tests/Mooc/Steps/Domain/Quiz/QuizStepQuestionMother.php b/tests/Mooc/Steps/Domain/Quiz/QuizStepQuestionMother.php new file mode 100644 index 000000000..04fcd8005 --- /dev/null +++ b/tests/Mooc/Steps/Domain/Quiz/QuizStepQuestionMother.php @@ -0,0 +1,20 @@ + WordMother::create()) + ); + } +} diff --git a/tests/Mooc/Steps/Domain/StepDurationMother.php b/tests/Mooc/Steps/Domain/StepDurationMother.php new file mode 100644 index 000000000..73ce6842d --- /dev/null +++ b/tests/Mooc/Steps/Domain/StepDurationMother.php @@ -0,0 +1,16 @@ +repository()->save($step); + } + + /** + * @test + * @dataProvider steps + */ + public function it_should_search_an_existing_step(Step $step): void + { + $this->repository()->save($step); + + $this->assertEquals($step, $this->repository()->search($step->id)); + } + + /** + * @test + * @dataProvider steps + */ + public function it_should_delete_an_existing_step(Step $step): void + { + $this->repository()->save($step); + $this->repository()->delete($step); + + $this->assertNull($this->repository()->search($step->id)); + } + + public function steps(): array + { + return [ + 'video' => [VideoStepMother::create()], + 'exercise' => [ExerciseStepMother::create()], + 'quiz' => [QuizStepMother::create()], + ]; + } +} diff --git a/tests/Mooc/Steps/StepsModuleInfrastructureTestCase.php b/tests/Mooc/Steps/StepsModuleInfrastructureTestCase.php new file mode 100644 index 000000000..8d0eb52ca --- /dev/null +++ b/tests/Mooc/Steps/StepsModuleInfrastructureTestCase.php @@ -0,0 +1,16 @@ +service(StepRepository::class); + } +} diff --git a/tests/apps/mooc/frontend/.gitkeep b/tests/Mooc/Videos/Application/.gitkeep similarity index 100% rename from tests/apps/mooc/frontend/.gitkeep rename to tests/Mooc/Videos/Application/.gitkeep diff --git a/tests/src/Mooc/Shared/Domain/.gitkeep b/tests/Mooc/Videos/Domain/.gitkeep similarity index 100% rename from tests/src/Mooc/Shared/Domain/.gitkeep rename to tests/Mooc/Videos/Domain/.gitkeep diff --git a/tests/src/Mooc/Videos/Application/.gitkeep b/tests/Mooc/Videos/Infrastructure/.gitkeep similarity index 100% rename from tests/src/Mooc/Videos/Application/.gitkeep rename to tests/Mooc/Videos/Infrastructure/.gitkeep diff --git a/tests/Shared/Domain/Criteria/CriteriaMother.php b/tests/Shared/Domain/Criteria/CriteriaMother.php new file mode 100644 index 000000000..b46d45089 --- /dev/null +++ b/tests/Shared/Domain/Criteria/CriteriaMother.php @@ -0,0 +1,26 @@ +getName()])) { + $property->setAccessible(true); + $property->setValue($duplicated, $newParams[$property->getName()]); + } + }, + $reflection->getProperties() + ); + + return $duplicated; + } +} diff --git a/tests/Shared/Domain/IntegerMother.php b/tests/Shared/Domain/IntegerMother.php new file mode 100644 index 000000000..910a08cc8 --- /dev/null +++ b/tests/Shared/Domain/IntegerMother.php @@ -0,0 +1,23 @@ +numberBetween($min, $max); + } + + public static function lessThan(int $max): int + { + return self::between(1, $max); + } +} diff --git a/tests/Shared/Domain/MotherCreator.php b/tests/Shared/Domain/MotherCreator.php new file mode 100644 index 000000000..f2da13923 --- /dev/null +++ b/tests/Shared/Domain/MotherCreator.php @@ -0,0 +1,18 @@ +randomElement($elements); + } +} diff --git a/tests/Shared/Domain/Repeater.php b/tests/Shared/Domain/Repeater.php new file mode 100644 index 000000000..19781e14f --- /dev/null +++ b/tests/Shared/Domain/Repeater.php @@ -0,0 +1,20 @@ +evaluate($actual, '', true); + } + + public static function assertSimilar(mixed $expected, mixed $actual): void + { + $constraint = new CodelyTvConstraintIsSimilar($expected); + + $constraint->evaluate($actual); + } + + public static function similarTo(mixed $value, float $delta = 0.0): CodelyTvMatcherIsSimilar + { + return new CodelyTvMatcherIsSimilar($value, $delta); + } +} diff --git a/tests/Shared/Domain/UuidMother.php b/tests/Shared/Domain/UuidMother.php new file mode 100644 index 000000000..20bd3516c --- /dev/null +++ b/tests/Shared/Domain/UuidMother.php @@ -0,0 +1,13 @@ +unique()->uuid; + } +} diff --git a/tests/Shared/Domain/WordMother.php b/tests/Shared/Domain/WordMother.php new file mode 100644 index 000000000..4bf988e35 --- /dev/null +++ b/tests/Shared/Domain/WordMother.php @@ -0,0 +1,13 @@ +word; + } +} diff --git a/tests/Shared/Infrastructure/ArchitectureTest.php b/tests/Shared/Infrastructure/ArchitectureTest.php new file mode 100644 index 000000000..6a82b2b18 --- /dev/null +++ b/tests/Shared/Infrastructure/ArchitectureTest.php @@ -0,0 +1,40 @@ +sessionHelper = new MinkHelper($this->minkSession); + $this->request = new MinkSessionRequestHelper(new MinkHelper($minkSession)); + } + + /** + * @Given I send a :method request to :url + */ + public function iSendARequestTo(string $method, string $url): void + { + $this->request->sendRequest($method, $this->locatePath($url)); + } + + /** + * @Given I send a :method request to :url with body: + */ + public function iSendARequestToWithBody(string $method, string $url, PyStringNode $body): void + { + $this->request->sendRequestWithPyStringNode($method, $this->locatePath($url), $body); + } + + /** + * @Then the response content should be: + */ + public function theResponseContentShouldBe(PyStringNode $expectedResponse): void + { + $expected = $this->sanitizeOutput($expectedResponse->getRaw()); + $actual = $this->sanitizeOutput($this->sessionHelper->getResponse()); + + if ($expected === false || $actual === false) { + throw new RuntimeException('The outputs could not be parsed as JSON'); + } + + if ($expected !== $actual) { + throw new RuntimeException( + sprintf("The outputs does not match!\n\n-- Expected:\n%s\n\n-- Actual:\n%s", $expected, $actual) + ); + } + } + + /** + * @Then the response should be empty + */ + public function theResponseShouldBeEmpty(): void + { + $actual = trim($this->sessionHelper->getResponse()); + + if (!empty($actual)) { + throw new RuntimeException(sprintf("The outputs is not empty, Actual:\n%s", $actual)); + } + } + + /** + * @Then print last api response + */ + public function printApiResponse(): void + { + print_r($this->sessionHelper->getResponse()); + } + + /** + * @Then print response headers + */ + public function printResponseHeaders(): void + { + print_r($this->sessionHelper->getResponseHeaders()); + } + + /** + * @Then the response status code should be :expectedResponseCode + */ + public function theResponseStatusCodeShouldBe(mixed $expectedResponseCode): void + { + if ($this->minkSession->getStatusCode() !== (int) $expectedResponseCode) { + throw new RuntimeException( + sprintf( + 'The status code <%s> does not match the expected <%s>', + $this->minkSession->getStatusCode(), + $expectedResponseCode + ) + ); + } + } + + private function sanitizeOutput(string $output): false | string + { + return json_encode(json_decode(trim($output), true, 512, JSON_THROW_ON_ERROR), JSON_THROW_ON_ERROR); + } +} diff --git a/tests/Shared/Infrastructure/Behat/ApplicationFeatureContext.php b/tests/Shared/Infrastructure/Behat/ApplicationFeatureContext.php new file mode 100644 index 000000000..29d6788a8 --- /dev/null +++ b/tests/Shared/Infrastructure/Behat/ApplicationFeatureContext.php @@ -0,0 +1,37 @@ +connections->clear(); + $this->connections->truncate(); + } + + /** + * @Given /^I send an event to the event bus:$/ + */ + public function iSendAnEventToTheEventBus(PyStringNode $event): void + { + $domainEvent = $this->deserializer->deserialize($event->getRaw()); + + $this->bus->publish($domainEvent); + } +} diff --git a/tests/src/Shared/Infrastructure/Bus/Command/FakeCommand.php b/tests/Shared/Infrastructure/Bus/Command/FakeCommand.php similarity index 61% rename from tests/src/Shared/Infrastructure/Bus/Command/FakeCommand.php rename to tests/Shared/Infrastructure/Bus/Command/FakeCommand.php index 52d38173f..d009d2a79 100644 --- a/tests/src/Shared/Infrastructure/Bus/Command/FakeCommand.php +++ b/tests/Shared/Infrastructure/Bus/Command/FakeCommand.php @@ -1,11 +1,9 @@ commandBus = new InMemorySymfonyCommandBus([$this->commandHandler()]); + } + + /** @test */ + public function it_should_be_able_to_handle_a_command(): void + { + $this->expectException(RuntimeException::class); + + $this->commandBus->dispatch(new FakeCommand()); + } + + /** @test */ + public function it_should_raise_an_exception_dispatching_a_non_registered_command(): void + { + $this->expectException(CommandNotRegisteredError::class); + + $this->commandBus->dispatch($this->command()); + } + + private function commandHandler(): object + { + return new class() { + public function __invoke(FakeCommand $command): never + { + throw new RuntimeException('This works fine!'); + } + }; + } + + private function command(): Command | MockInterface + { + return $this->mock(Command::class); + } +} diff --git a/tests/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php b/tests/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php new file mode 100644 index 000000000..ca17d6ceb --- /dev/null +++ b/tests/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php @@ -0,0 +1,51 @@ +bus = new MySqlDoctrineEventBus($this->service(EntityManager::class)); + $this->consumer = new MySqlDoctrineDomainEventsConsumer( + $this->service(EntityManager::class), + $this->service(DomainEventMapping::class) + ); + } + + /** @test */ + public function it_should_publish_and_consume_domain_events_from_msql(): void + { + $domainEvent = CourseCreatedDomainEventMother::create(); + $anotherDomainEvent = CoursesCounterIncrementedDomainEventMother::create(); + + $this->bus->publish($domainEvent, $anotherDomainEvent); + + $this->consumer->consume( + subscribers: fn (DomainEvent ...$expectedEvents) => $this->assertContainsEquals($domainEvent, $expectedEvents), + eventsToConsume: 2 + ); + } + + protected function kernelClass(): string + { + return MoocBackendKernel::class; + } +} diff --git a/tests/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php new file mode 100644 index 000000000..b3d5b09e8 --- /dev/null +++ b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php @@ -0,0 +1,181 @@ +connection = $this->service(RabbitMqConnection::class); + + $this->exchangeName = 'test_domain_events'; + $this->configurer = new RabbitMqConfigurer($this->connection); + $this->publisher = new RabbitMqEventBus( + $this->connection, + $this->exchangeName, + $this->service(MySqlDoctrineEventBus::class) + ); + $this->consumer = new RabbitMqDomainEventsConsumer( + $this->connection, + $this->service(DomainEventJsonDeserializer::class), + $this->exchangeName, + $maxRetries = 1 + ); + $this->fakeSubscriber = new TestAllWorksOnRabbitMqEventsPublished(); + $this->consumerHasBeenExecuted = false; + + $this->cleanEnvironment($this->connection); + } + + /** @test */ + public function it_should_publish_and_consume_domain_events_from_rabbitmq(): void + { + $domainEvent = CourseCreatedDomainEventMother::create(); + + $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); + + $this->publisher->publish($domainEvent); + + $this->consumer->consume( + $this->assertConsumer($domainEvent), + RabbitMqQueueNameFormatter::format($this->fakeSubscriber) + ); + + $this->assertTrue($this->consumerHasBeenExecuted); + } + + /** @test */ + public function it_should_throw_an_exception_consuming_non_existing_domain_events(): void + { + $this->expectException(RuntimeException::class); + + $domainEvent = CoursesCounterIncrementedDomainEventMother::create(); + + $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); + + $this->publisher->publish($domainEvent); + + $this->consumer->consume( + $this->assertConsumer($domainEvent), + RabbitMqQueueNameFormatter::format($this->fakeSubscriber) + ); + + $this->assertTrue($this->consumerHasBeenExecuted); + } + + /** @test */ + public function it_should_retry_failed_domain_events(): void + { + $domainEvent = CourseCreatedDomainEventMother::create(); + + $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); + + $this->publisher->publish($domainEvent); + + $this->simulateErrorConsuming(); + + sleep(1); + + $this->consumer->consume( + $this->assertConsumer($domainEvent), + RabbitMqQueueNameFormatter::format($this->fakeSubscriber) + ); + + $this->assertTrue($this->consumerHasBeenExecuted); + } + + /** @test */ + public function it_should_send_events_to_dead_letter_after_retry_failed_domain_events(): void + { + $domainEvent = CourseCreatedDomainEventMother::create(); + + $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); + + $this->publisher->publish($domainEvent); + + $this->simulateErrorConsuming(); + + sleep(1); + + $this->simulateErrorConsuming(); + + $this->assertDeadLetterContainsEvent(1); + } + + protected function kernelClass(): string + { + return MoocBackendKernel::class; + } + + private function assertConsumer(DomainEvent ...$expectedDomainEvents): callable + { + return function (DomainEvent $domainEvent) use ($expectedDomainEvents): void { + $this->assertContainsEquals($domainEvent, $expectedDomainEvents); + + $this->consumerHasBeenExecuted = true; + }; + } + + private function failingConsumer(): callable + { + return static function (DomainEvent $domainEvent): never { + throw new RuntimeException('To test'); + }; + } + + private function simulateErrorConsuming(): void + { + try { + $this->consumer->consume($this->failingConsumer(), RabbitMqQueueNameFormatter::format($this->fakeSubscriber)); + } catch (Throwable $error) { + $this->assertInstanceOf(RuntimeException::class, $error); + } + } + + private function cleanEnvironment(RabbitMqConnection $connection): void + { + $connection->queue(RabbitMqQueueNameFormatter::format($this->fakeSubscriber))->delete(); + $connection->queue(RabbitMqQueueNameFormatter::formatRetry($this->fakeSubscriber))->delete(); + $connection->queue(RabbitMqQueueNameFormatter::formatDeadLetter($this->fakeSubscriber))->delete(); + } + + private function assertDeadLetterContainsEvent(int $expectedNumberOfEvents): void + { + $totalEventsInDeadLetter = 0; + + while ($this->connection->queue(RabbitMqQueueNameFormatter::formatDeadLetter($this->fakeSubscriber))->get( + AMQP_AUTOACK + )) { + $totalEventsInDeadLetter++; + } + + $this->assertSame($expectedNumberOfEvents, $totalEventsInDeadLetter); + } +} diff --git a/tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php similarity index 50% rename from tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php rename to tests/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php index f32ca0cd7..937f28e08 100644 --- a/tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php +++ b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php @@ -1,6 +1,6 @@ number; + } +} diff --git a/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php b/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php new file mode 100644 index 000000000..dc3f2095b --- /dev/null +++ b/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php @@ -0,0 +1,55 @@ +queryBus = new InMemorySymfonyQueryBus([$this->queryHandler()]); + } + + /** @test */ + public function it_should_return_a_response_successfully(): void + { + $this->expectException(RuntimeException::class); + + $this->queryBus->ask(new FakeQuery()); + } + + /** @test */ + public function it_should_raise_an_exception_dispatching_a_non_registered_query(): void + { + $this->expectException(QueryNotRegisteredError::class); + + $this->queryBus->ask($this->query()); + } + + private function queryHandler(): object + { + return new class() { + public function __invoke(FakeQuery $query): never + { + throw new RuntimeException('This works fine!'); + } + }; + } + + private function query(): MockInterface | Query + { + return $this->mock(Query::class); + } +} diff --git a/tests/src/Shared/Infrastructure/ConstantRandomNumberGenerator.php b/tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php similarity index 66% rename from tests/src/Shared/Infrastructure/ConstantRandomNumberGenerator.php rename to tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php index 49f42855f..810e216cc 100644 --- a/tests/src/Shared/Infrastructure/ConstantRandomNumberGenerator.php +++ b/tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php @@ -1,6 +1,6 @@ getConnection(); + + $tables = $this->tables($connection); + $truncateTablesSql = $this->truncateDatabaseSql($tables); + + $connection->executeQuery($truncateTablesSql); + } + + private function truncateDatabaseSql(array $tables): string + { + $truncateTables = map($this->truncateTableSql(), $tables); + + return sprintf('SET FOREIGN_KEY_CHECKS = 0; %s SET FOREIGN_KEY_CHECKS = 1;', implode(' ', $truncateTables)); + } + + private function truncateTableSql(): callable + { + return fn (array $table): string => sprintf('TRUNCATE TABLE `%s`;', (string) first($table)); + } + + private function tables(Connection $connection): array + { + return $connection->executeQuery('SHOW TABLES')->fetchAllAssociative(); + } +} diff --git a/tests/Shared/Infrastructure/Elastic/ElasticDatabaseCleaner.php b/tests/Shared/Infrastructure/Elastic/ElasticDatabaseCleaner.php new file mode 100644 index 000000000..293d5284a --- /dev/null +++ b/tests/Shared/Infrastructure/Elastic/ElasticDatabaseCleaner.php @@ -0,0 +1,27 @@ +client()->cat()->indices(); + + each( + static function (array $index) use ($client): void { + $indexName = $index['index']; + + $client->client()->indices()->delete(['index' => $indexName]); + $client->client()->indices()->create(['index' => $indexName]); + }, + $indices + ); + } +} diff --git a/tests/Shared/Infrastructure/Mink/MinkHelper.php b/tests/Shared/Infrastructure/Mink/MinkHelper.php new file mode 100644 index 000000000..ea443d6e1 --- /dev/null +++ b/tests/Shared/Infrastructure/Mink/MinkHelper.php @@ -0,0 +1,80 @@ + [], + 'files' => [], + 'server' => ['HTTP_ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json'], + 'content' => null, + 'changeHistory' => true, + ]; + + $optionalParams = array_merge($defaultOptionalParams, $optionalParams); + + $crawler = $this->getClient()->request( + $method, + $url, + $optionalParams['parameters'], + $optionalParams['files'], + $optionalParams['server'], + $optionalParams['content'], + $optionalParams['changeHistory'] + ); + + return $crawler; + } + + public function getResponse(): string + { + return $this->getSession()->getPage()->getContent(); + } + + public function getResponseHeaders(): array + { + return $this->normalizeHeaders(array_change_key_case($this->getSession()->getResponseHeaders(), CASE_LOWER)); + } + + public function resetServerParameters(): void + { + $this->getClient()->setServerParameters([]); + } + + public function getRequest(): object + { + return $this->getClient()->getRequest(); + } + + private function getSession(): Session + { + return $this->session; + } + + private function getDriver(): DriverInterface + { + return $this->getSession()->getDriver(); + } + + private function getClient(): AbstractBrowser + { + return $this->getDriver()->getClient(); + } + + private function normalizeHeaders(array $headers): array + { + return array_map('implode', array_filter($headers)); + } +} diff --git a/tests/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php b/tests/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php new file mode 100644 index 000000000..df914dd98 --- /dev/null +++ b/tests/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php @@ -0,0 +1,28 @@ +request($method, $url, $optionalParams); + } + + public function sendRequestWithPyStringNode($method, $url, PyStringNode $body): void + { + $this->request($method, $url, ['content' => $body->getRaw()]); + } + + public function request(string $method, string $url, array $optionalParams = []): Crawler + { + return $this->sessionHelper->sendRequest($method, $url, $optionalParams); + } +} diff --git a/tests/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php b/tests/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php new file mode 100644 index 000000000..1737b7adb --- /dev/null +++ b/tests/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php @@ -0,0 +1,29 @@ +constraint = new CodelyTvConstraintIsSimilar($value, $delta); + } + + public function match(&$actual): bool + { + return $this->constraint->evaluate($actual, '', true); + } + + public function __toString(): string + { + return 'Is similar'; + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php new file mode 100644 index 000000000..22ee1fccf --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php @@ -0,0 +1,49 @@ +contains($expected, $actual) || count($expected) !== count($actual)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting the collection of AGs contains all the expected elements.' + ); + } + } + + private function contains(array $expectedArray, array $actualArray): bool + { + $exists = fn (AggregateRoot $expected): bool => any( + fn (AggregateRoot $actual): bool => TestUtils::isSimilar($expected, $actual), + $actualArray + ); + + return all($exists, $expectedArray); + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php new file mode 100644 index 000000000..aefd86a36 --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php @@ -0,0 +1,74 @@ +pullDomainEvents(); + + if (!$this->aggregateRootsAreSimilar($expected, $actualEntity)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting the aggregate roots are equal.' + ); + } + } + + private function aggregateRootsAreSimilar(AggregateRoot $expected, AggregateRoot $actual): bool + { + if (!$this->aggregateRootsAreTheSameClass($expected, $actual)) { + return false; + } + + return $this->aggregateRootPropertiesAreSimilar($expected, $actual); + } + + private function aggregateRootsAreTheSameClass(AggregateRoot $expected, AggregateRoot $actual): bool + { + return $expected::class === $actual::class; + } + + private function aggregateRootPropertiesAreSimilar(AggregateRoot $expected, AggregateRoot $actual): bool + { + $expectedReflected = new ReflectionObject($expected); + $actualReflected = new ReflectionObject($actual); + + foreach ($expectedReflected->getProperties() as $expectedReflectedProperty) { + $actualReflectedProperty = $actualReflected->getProperty($expectedReflectedProperty->getName()); + + $expectedReflectedProperty->setAccessible(true); + $actualReflectedProperty->setAccessible(true); + + $expectedProperty = $expectedReflectedProperty->getValue($expected); + $actualProperty = $actualReflectedProperty->getValue($actual); + + if (!TestUtils::isSimilar($expectedProperty, $actualProperty)) { + return false; + } + } + + return true; + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php new file mode 100644 index 000000000..67d3b5605 --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php @@ -0,0 +1,50 @@ +sub($intervalWithDelta) || $actual > $expectedUpper->add($intervalWithDelta)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->dateTimeToString($expected), + $this->dateTimeToString($actual), + false, + 'Failed asserting that two DateTime objects are equal.' + ); + } + } + + protected function dateTimeToString(DateTimeInterface $datetime): string + { + return $datetime->format(DateTime::ATOM) ?: 'Invalid DateTime object'; + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php new file mode 100644 index 000000000..546c75cb9 --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php @@ -0,0 +1,71 @@ +isValidDateTimeString($expected) + && $this->isValidDateTimeString($actual); + } + + public function assertEquals( + $expected, + $actual, + $delta = 0.0, + $canonicalize = false, + $ignoreCase = false, + array &$processed = [] + ): void { + $expectedDate = new DateTimeImmutable($expected); + $actualDate = new DateTimeImmutable($actual); + + $normalizedDelta = $delta === 0.0 ? 10 : $delta; + $intervalWithDelta = new DateInterval(sprintf('PT%sS', abs($normalizedDelta))); + + if ($actualDate < $expectedDate->sub($intervalWithDelta) + || $actualDate > $expectedDate->add($intervalWithDelta)) { + throw new ComparisonFailure( + $expectedDate, + $actualDate, + $this->dateTimeToString($expectedDate), + $this->dateTimeToString($actualDate), + false, + 'Failed asserting that two DateTime strings are equal.' + ); + } + } + + protected function dateTimeToString(DateTimeInterface $datetime): string + { + $string = $datetime->format(DateTime::ATOM); + + return $string ?: 'Invalid DateTime object'; + } + + private function isValidDateTimeString(string $expected): bool + { + $isValid = true; + + try { + new DateTimeImmutable($expected); + } catch (Throwable) { + $isValid = false; + } + + return $isValid; + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php new file mode 100644 index 000000000..c191f7fb0 --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php @@ -0,0 +1,49 @@ +contains($expected, $actual) || count($expected) !== count($actual)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting the collection of Events contains all the expected elements.' + ); + } + } + + private function contains(array $expectedArray, array $actualArray): bool + { + $exists = static fn (DomainEvent $expected): bool => any( + static fn (DomainEvent $actual): bool => TestUtils::isSimilar($expected, $actual), + $actualArray + ); + + return all($exists, $expectedArray); + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php new file mode 100644 index 000000000..f14cb6a77 --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php @@ -0,0 +1,75 @@ +areSimilar($expected, $actual)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting the events are equal.' + ); + } + } + + private function areSimilar(DomainEvent $expected, DomainEvent $actual): bool + { + if (!$this->areTheSameClass($expected, $actual)) { + return false; + } + + return $this->propertiesAreSimilar($expected, $actual); + } + + private function areTheSameClass(DomainEvent $expected, DomainEvent $actual): bool + { + return $expected::class === $actual::class; + } + + private function propertiesAreSimilar(DomainEvent $expected, DomainEvent $actual): bool + { + $expectedReflected = new ReflectionObject($expected); + $actualReflected = new ReflectionObject($actual); + + foreach ($expectedReflected->getProperties() as $expectedReflectedProperty) { + if (!in_array($expectedReflectedProperty->getName(), self::$ignoredAttributes, false)) { + $actualReflectedProperty = $actualReflected->getProperty($expectedReflectedProperty->getName()); + + $expectedReflectedProperty->setAccessible(true); + $actualReflectedProperty->setAccessible(true); + + $expectedProperty = $expectedReflectedProperty->getValue($expected); + $actualProperty = $actualReflectedProperty->getValue($actual); + + if (!TestUtils::isSimilar($expectedProperty, $actualProperty)) { + return false; + } + } + } + + return true; + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php b/tests/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php new file mode 100644 index 000000000..e5b4ca53a --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php @@ -0,0 +1,75 @@ +value === $other) { + return true; + } + + $isValid = true; + $comparatorFactory = new Factory(); + + $comparatorFactory->register(new AggregateRootArraySimilarComparator()); + $comparatorFactory->register(new AggregateRootSimilarComparator()); + $comparatorFactory->register(new DomainEventArraySimilarComparator()); + $comparatorFactory->register(new DomainEventSimilarComparator()); + $comparatorFactory->register(new DateTimeSimilarComparator()); + $comparatorFactory->register(new DateTimeStringSimilarComparator()); + + try { + $comparator = $comparatorFactory->getComparatorFor($other, $this->value); + + $comparator->assertEquals($this->value, $other, $this->delta); + } catch (ComparisonFailure $f) { + if (!$returnResult) { + throw new ExpectationFailedException(trim($description . "\n" . $f->getMessage()), $f); + } + + $isValid = false; + } + + return $isValid; + } + + public function toString(): string + { + $delta = ''; + + if (is_string($this->value)) { + if (str_contains($this->value, "\n")) { + return 'is equal to '; + } + + return sprintf("is equal to '%s'", $this->value); + } + + if ($this->delta !== 0) { + $delta = sprintf(' with delta <%F>', $this->delta); + } + + return sprintf('is equal to %s%s', $this->exporter()->export($this->value), $delta); + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php b/tests/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php new file mode 100644 index 000000000..52438a8f0 --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php @@ -0,0 +1,64 @@ +kernelClass(); + + self::bootKernel(['environment' => 'test']); + + parent::setUp(); + } + + protected function assertSimilar(mixed $expected, mixed $actual): void + { + TestUtils::assertSimilar($expected, $actual); + } + + protected function service(string $id): mixed + { + return self::getContainer()->get($id); + } + + protected function parameter(string $parameter): mixed + { + return self::getContainer()->getParameter($parameter); + } + + protected function clearUnitOfWork(): void + { + $this->service(EntityManager::class)->clear(); + } + + /** @param int<0, max> $timeToWaitOnErrorInSeconds */ + protected function eventually( + callable $fn, + int $totalRetries = 3, + int $timeToWaitOnErrorInSeconds = 1, + int $attempt = 0 + ): void { + try { + $fn(); + } catch (Throwable $error) { + if ($totalRetries === $attempt) { + throw $error; + } + + sleep($timeToWaitOnErrorInSeconds); + + $this->eventually($fn, $totalRetries, $timeToWaitOnErrorInSeconds, $attempt + 1); + } + } +} diff --git a/tests/Shared/Infrastructure/PhpUnit/UnitTestCase.php b/tests/Shared/Infrastructure/PhpUnit/UnitTestCase.php new file mode 100644 index 000000000..599f2005d --- /dev/null +++ b/tests/Shared/Infrastructure/PhpUnit/UnitTestCase.php @@ -0,0 +1,104 @@ +eventBus() + ->shouldReceive('publish') + ->with($this->similarTo($domainEvent)) + ->andReturnNull(); + } + + protected function shouldNotPublishDomainEvent(): void + { + $this->eventBus() + ->shouldReceive('publish') + ->withNoArgs() + ->andReturnNull(); + } + + protected function eventBus(): EventBus | MockInterface + { + return $this->eventBus ??= $this->mock(EventBus::class); + } + + protected function shouldGenerateUuid(string $uuid): void + { + $this->uuidGenerator() + ->shouldReceive('generate') + ->once() + ->withNoArgs() + ->andReturn($uuid); + } + + protected function uuidGenerator(): MockInterface | UuidGenerator + { + return $this->uuidGenerator ??= $this->mock(UuidGenerator::class); + } + + protected function notify(DomainEvent $event, callable $subscriber): void + { + $subscriber($event); + } + + protected function dispatch(Command $command, callable $commandHandler): void + { + $commandHandler($command); + } + + protected function assertAskResponse(Response $expected, Query $query, callable $queryHandler): void + { + $actual = $queryHandler($query); + + $this->assertEquals($expected, $actual); + } + + /** @param class-string $expectedErrorClass */ + protected function assertAskThrowsException(string $expectedErrorClass, Query $query, callable $queryHandler): void + { + $this->expectException($expectedErrorClass); + + $queryHandler($query); + } + + protected function isSimilar(mixed $expected, mixed $actual): bool + { + return TestUtils::isSimilar($expected, $actual); + } + + protected function assertSimilar(mixed $expected, mixed $actual): void + { + TestUtils::assertSimilar($expected, $actual); + } + + protected function similarTo(mixed $value, float $delta = 0.0): CodelyTvMatcherIsSimilar + { + return TestUtils::similarTo($value, $delta); + } +} diff --git a/tests/Shared/SharedArchitectureTest.php b/tests/Shared/SharedArchitectureTest.php new file mode 100644 index 000000000..da8b1e751 --- /dev/null +++ b/tests/Shared/SharedArchitectureTest.php @@ -0,0 +1,62 @@ +classes(Selector::inNamespace('CodelyTv\Shared\Domain')) + ->canOnlyDependOn() + ->classes(...array_merge(ArchitectureTest::languageClasses(), [ + // Itself + Selector::inNamespace('CodelyTv\Shared\Domain'), + // Dependencies treated as domain + Selector::classname(Uuid::class), + ])) + ->because('shared domain cannot import from outside'); + } + + public function test_shared_infrastructure_should_not_import_from_other_contexts(): Rule + { + return PHPat::rule() + ->classes(Selector::inNamespace('CodelyTv\Shared\Infrastructure')) + ->shouldNotDependOn() + ->classes(Selector::inNamespace('CodelyTv')) + ->excluding( + // Itself + Selector::inNamespace('CodelyTv\Shared'), + // This need to be refactored + Selector::classname(MySqlDatabaseCleaner::class), + Selector::classname(AuthenticateUserCommand::class), + Selector::inNamespace('CodelyTv\Backoffice\Auth'), + ); + } + + public function test_all_use_cases_can_only_have_one_public_method(): Rule + { + return PHPat::rule() + ->classes( + Selector::classname('/^CodelyTv\\\\.+\\\\.+\\\\Application\\\\.+\\\\(?!.*(?:Command|Query)$).*$/', true) + ) + ->excluding( + Selector::implements(Response::class), + Selector::implements(DomainEventSubscriber::class), + Selector::inNamespace('/.*\\\\Tests\\\\.*/', true) + ) + ->shouldHaveOnlyOnePublicMethod(); + } +} diff --git a/tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php b/tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php deleted file mode 100644 index 73b4ecccc..000000000 --- a/tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php +++ /dev/null @@ -1,62 +0,0 @@ -handler = new AuthenticateUserCommandHandler(new UserAuthenticator($this->repository())); - } - - /** @test */ - public function it_should_authenticate_a_valid_user(): void - { - $command = AuthenticateUserCommandMother::random(); - $authUser = AuthUserMother::fromCommand($command); - - $this->shouldSearch($authUser->username(), $authUser); - - $this->dispatch($command, $this->handler); - } - - /** @test */ - public function it_should_throw_an_exception_when_the_user_does_not_exist(): void - { - $this->expectException(InvalidAuthUsername::class); - - $command = AuthenticateUserCommandMother::random(); - $username = AuthUsernameMother::create($command->username()); - - $this->shouldSearch($username); - - $this->dispatch($command, $this->handler); - } - - /** @test */ - public function it_should_throw_an_exception_when_the_password_does_not_math(): void - { - $this->expectException(InvalidAuthCredentials::class); - - $command = AuthenticateUserCommandMother::random(); - $authUser = AuthUserMother::withUsername(AuthUsernameMother::create($command->username())); - - $this->shouldSearch($authUser->username(), $authUser); - - $this->dispatch($command, $this->handler); - } -} diff --git a/tests/src/Backoffice/Auth/AuthModuleUnitTestCase.php b/tests/src/Backoffice/Auth/AuthModuleUnitTestCase.php deleted file mode 100644 index 3e8f60632..000000000 --- a/tests/src/Backoffice/Auth/AuthModuleUnitTestCase.php +++ /dev/null @@ -1,31 +0,0 @@ -repository() - ->shouldReceive('search') - ->with($this->similarTo($username)) - ->once() - ->andReturn($authUser); - } - - /** @return AuthRepository|MockInterface */ - protected function repository(): MockInterface - { - return $this->repository = $this->repository ?: $this->mock(AuthRepository::class); - } -} diff --git a/tests/src/Backoffice/Auth/Domain/AuthPasswordMother.php b/tests/src/Backoffice/Auth/Domain/AuthPasswordMother.php deleted file mode 100644 index e6a30608b..000000000 --- a/tests/src/Backoffice/Auth/Domain/AuthPasswordMother.php +++ /dev/null @@ -1,21 +0,0 @@ -username()), - AuthPasswordMother::create($command->password()) - ); - } - - public static function withUsername(AuthUsername $username): AuthUser - { - return self::create($username, AuthPasswordMother::random()); - } - - public static function random(): AuthUser - { - return self::create(AuthUsernameMother::random(), AuthPasswordMother::random()); - } -} diff --git a/tests/src/Backoffice/Auth/Domain/AuthUsernameMother.php b/tests/src/Backoffice/Auth/Domain/AuthUsernameMother.php deleted file mode 100644 index ac05dcf7e..000000000 --- a/tests/src/Backoffice/Auth/Domain/AuthUsernameMother.php +++ /dev/null @@ -1,21 +0,0 @@ -service(EntityManager::class)); - } -} diff --git a/tests/src/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php b/tests/src/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php deleted file mode 100644 index eab5f178c..000000000 --- a/tests/src/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php +++ /dev/null @@ -1,28 +0,0 @@ - 'name', - 'operator' => 'CONTAINS', - 'value' => $text, - ] - ) - ) - ); - } -} diff --git a/tests/src/Backoffice/Courses/Domain/BackofficeCourseMother.php b/tests/src/Backoffice/Courses/Domain/BackofficeCourseMother.php deleted file mode 100644 index 2341a63de..000000000 --- a/tests/src/Backoffice/Courses/Domain/BackofficeCourseMother.php +++ /dev/null @@ -1,36 +0,0 @@ -value(), - $name, - CourseDurationMother::random()->value() - ); - } - - public static function random(): BackofficeCourse - { - return self::create( - CourseIdMother::random()->value(), - CourseNameMother::random()->value(), - CourseDurationMother::random()->value() - ); - } -} diff --git a/tests/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php b/tests/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php deleted file mode 100644 index c4c770e62..000000000 --- a/tests/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php +++ /dev/null @@ -1,64 +0,0 @@ -repository()->save(BackofficeCourseMother::random()); - } - - /** @test */ - public function it_should_search_all_existing_courses(): void - { - $existingCourse = BackofficeCourseMother::random(); - $anotherExistingCourse = BackofficeCourseMother::random(); - $existingCourses = [$existingCourse, $anotherExistingCourse]; - - $this->repository()->save($existingCourse); - $this->repository()->save($anotherExistingCourse); - - $this->assertSimilar($existingCourses, $this->repository()->searchAll()); - } - - /** @test */ - public function it_should_search_all_existing_courses_with_an_empty_criteria(): void - { - $existingCourse = BackofficeCourseMother::random(); - $anotherExistingCourse = BackofficeCourseMother::random(); - $existingCourses = [$existingCourse, $anotherExistingCourse]; - - $this->repository()->save($existingCourse); - $this->repository()->save($anotherExistingCourse); - $this->clearUnitOfWork(); - - $this->assertSimilar($existingCourses, $this->repository()->matching(CriteriaMother::empty())); - } - - /** @test */ - public function it_should_filter_by_criteria(): void - { - $dddInPhpCourse = BackofficeCourseMother::withName('DDD en PHP'); - $dddInJavaCourse = BackofficeCourseMother::withName('DDD en Java'); - $intellijCourse = BackofficeCourseMother::withName('Exprimiendo Intellij'); - $dddCourses = [$dddInPhpCourse, $dddInJavaCourse]; - - $nameContainsDddCriteria = BackofficeCourseCriteriaMother::nameContains('DDD'); - - $this->repository()->save($dddInJavaCourse); - $this->repository()->save($dddInPhpCourse); - $this->repository()->save($intellijCourse); - $this->clearUnitOfWork(); - - $this->assertSimilar($dddCourses, $this->repository()->matching($nameContainsDddCriteria)); - } -} diff --git a/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php b/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php deleted file mode 100644 index 42da14356..000000000 --- a/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php +++ /dev/null @@ -1,37 +0,0 @@ -handler = new CreateCourseCommandHandler(new CourseCreator($this->repository(), $this->eventBus())); - } - - /** @test */ - public function it_should_create_a_valid_course(): void - { - $command = CreateCourseCommandMother::random(); - - $course = CourseMother::fromRequest($command); - $domainEvent = CourseCreatedDomainEventMother::fromCourse($course); - - $this->shouldSave($course); - $this->shouldPublishDomainEvent($domainEvent); - - $this->dispatch($command, $this->handler); - } -} diff --git a/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandMother.php b/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandMother.php deleted file mode 100644 index 3bbc3bfc0..000000000 --- a/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandMother.php +++ /dev/null @@ -1,26 +0,0 @@ -value(), $name->value(), $duration->value()); - } - - public static function random(): CreateCourseCommand - { - return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random()); - } -} diff --git a/tests/src/Mooc/Courses/Application/Update/CourseRenamerTest.php b/tests/src/Mooc/Courses/Application/Update/CourseRenamerTest.php deleted file mode 100644 index 1aa2cec80..000000000 --- a/tests/src/Mooc/Courses/Application/Update/CourseRenamerTest.php +++ /dev/null @@ -1,51 +0,0 @@ -renamer = new CourseRenamer($this->repository(), $this->eventBus()); - } - - /** @test */ - public function it_should_rename_an_existing_course(): void - { - $course = CourseMother::random(); - $newName = CourseNameMother::random(); - $renamedCourse = DuplicatorMother::with($course, ['name' => $newName]); - - $this->shouldSearch($course->id(), $course); - $this->shouldSave($renamedCourse); - $this->shouldNotPublishDomainEvent(); - - $this->renamer->__invoke($course->id(), $newName); - } - - /** @test */ - public function it_should_throw_an_exception_when_the_course_not_exist(): void - { - $this->expectException(CourseNotExist::class); - - $id = CourseIdMother::random(); - - $this->shouldSearch($id, null); - - $this->renamer->__invoke($id, CourseNameMother::random()); - } -} diff --git a/tests/src/Mooc/Courses/CoursesModuleUnitTestCase.php b/tests/src/Mooc/Courses/CoursesModuleUnitTestCase.php deleted file mode 100644 index e0b21ff88..000000000 --- a/tests/src/Mooc/Courses/CoursesModuleUnitTestCase.php +++ /dev/null @@ -1,40 +0,0 @@ -repository() - ->shouldReceive('save') - ->with($this->similarTo($course)) - ->once() - ->andReturnNull(); - } - - protected function shouldSearch(CourseId $id, ?Course $course): void - { - $this->repository() - ->shouldReceive('search') - ->with($this->similarTo($id)) - ->once() - ->andReturn($course); - } - - /** @return CourseRepository|MockInterface */ - protected function repository(): MockInterface - { - return $this->repository = $this->repository ?: $this->mock(CourseRepository::class); - } -} diff --git a/tests/src/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php b/tests/src/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php deleted file mode 100644 index d15d3327b..000000000 --- a/tests/src/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php +++ /dev/null @@ -1,29 +0,0 @@ -value(), $name->value(), $duration->value()); - } - - public static function fromCourse(Course $course): CourseCreatedDomainEvent - { - return self::create($course->id(), $course->name(), $course->duration()); - } - - public static function random(): CourseCreatedDomainEvent - { - return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random()); - } -} diff --git a/tests/src/Mooc/Courses/Domain/CourseDurationMother.php b/tests/src/Mooc/Courses/Domain/CourseDurationMother.php deleted file mode 100644 index e2e598b1d..000000000 --- a/tests/src/Mooc/Courses/Domain/CourseDurationMother.php +++ /dev/null @@ -1,28 +0,0 @@ - self::random(); - } - - public static function random(): CourseId - { - return self::create(UuidMother::random()); - } -} diff --git a/tests/src/Mooc/Courses/Domain/CourseMother.php b/tests/src/Mooc/Courses/Domain/CourseMother.php deleted file mode 100644 index 9d991d53e..000000000 --- a/tests/src/Mooc/Courses/Domain/CourseMother.php +++ /dev/null @@ -1,33 +0,0 @@ -id()), - CourseNameMother::create($request->name()), - CourseDurationMother::create($request->duration()) - ); - } - - public static function random(): Course - { - return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random()); - } -} diff --git a/tests/src/Mooc/Courses/Domain/CourseNameMother.php b/tests/src/Mooc/Courses/Domain/CourseNameMother.php deleted file mode 100644 index d05d9d28c..000000000 --- a/tests/src/Mooc/Courses/Domain/CourseNameMother.php +++ /dev/null @@ -1,21 +0,0 @@ -repository()->save($course); - } - - /** @test */ - public function it_should_return_an_existing_course(): void - { - $course = CourseMother::random(); - - $this->repository()->save($course); - - $this->assertEquals($course, $this->repository()->search($course->id())); - } - - /** @test */ - public function it_should_not_return_a_non_existing_course(): void - { - $this->assertNull($this->repository()->search(CourseIdMother::random())); - } -} diff --git a/tests/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php b/tests/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php deleted file mode 100644 index 90243f897..000000000 --- a/tests/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ -handler = new FindCoursesCounterQueryHandler(new CoursesCounterFinder($this->repository())); - } - - /** @test */ - public function it_should_find_an_existing_courses_counter(): void - { - $counter = CoursesCounterMother::random(); - $query = new FindCoursesCounterQuery(); - $response = CoursesCounterResponseMother::create($counter->total()); - - $this->shouldSearch($counter); - - $this->assertAskResponse($response, $query, $this->handler); - } - - /** @test */ - public function it_should_throw_an_exception_when_courses_counter_does_not_exists(): void - { - $query = new FindCoursesCounterQuery(); - - $this->shouldSearch(null); - - $this->assertAskThrowsException(CoursesCounterNotExist::class, $query, $this->handler); - } -} diff --git a/tests/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php b/tests/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php deleted file mode 100644 index 3efc4b731..000000000 --- a/tests/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php +++ /dev/null @@ -1,78 +0,0 @@ -subscriber = new IncrementCoursesCounterOnCourseCreated( - new CoursesCounterIncrementer( - $this->repository(), - $this->uuidGenerator(), - $this->eventBus() - ) - ); - } - - /** @test */ - public function it_should_initialize_a_new_counter(): void - { - $event = CourseCreatedDomainEventMother::random(); - - $courseId = CourseIdMother::create($event->aggregateId()); - $newCounter = CoursesCounterMother::withOne($courseId); - $domainEvent = CoursesCounterIncrementedDomainEventMother::fromCounter($newCounter); - - $this->shouldSearch(null); - $this->shouldGenerateUuid($newCounter->id()->value()); - $this->shouldSave($newCounter); - $this->shouldPublishDomainEvent($domainEvent); - - $this->notify($event, $this->subscriber); - } - - /** @test */ - public function it_should_increment_an_existing_counter(): void - { - $event = CourseCreatedDomainEventMother::random(); - - $courseId = CourseIdMother::create($event->aggregateId()); - $existingCounter = CoursesCounterMother::random(); - $incrementedCounter = CoursesCounterMother::incrementing($existingCounter, $courseId); - $domainEvent = CoursesCounterIncrementedDomainEventMother::fromCounter($incrementedCounter); - - $this->shouldSearch($existingCounter); - $this->shouldSave($incrementedCounter); - $this->shouldPublishDomainEvent($domainEvent); - - $this->notify($event, $this->subscriber); - } - - /** @test */ - public function it_should_not_increment_an_already_incremented_course(): void - { - $event = CourseCreatedDomainEventMother::random(); - - $courseId = CourseIdMother::create($event->aggregateId()); - $existingCounter = CoursesCounterMother::withOne($courseId); - - $this->shouldSearch($existingCounter); - - $this->notify($event, $this->subscriber); - } -} diff --git a/tests/src/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php b/tests/src/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php deleted file mode 100644 index a6d3bae07..000000000 --- a/tests/src/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php +++ /dev/null @@ -1,38 +0,0 @@ -repository() - ->shouldReceive('save') - ->once() - ->with($this->similarTo($course)) - ->andReturnNull(); - } - - protected function shouldSearch(?CoursesCounter $counter): void - { - $this->repository() - ->shouldReceive('search') - ->once() - ->andReturn($counter); - } - - /** @return CoursesCounterRepository|MockInterface */ - protected function repository(): MockInterface - { - return $this->repository = $this->repository ?: $this->mock(CoursesCounterRepository::class); - } -} diff --git a/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php b/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php deleted file mode 100644 index b2d2f7b7a..000000000 --- a/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php +++ /dev/null @@ -1,21 +0,0 @@ -value(), $total->value()); - } - - public static function fromCounter(CoursesCounter $counter): CoursesCounterIncrementedDomainEvent - { - return self::create($counter->id(), $counter->total()); - } - - public static function random(): CoursesCounterIncrementedDomainEvent - { - return self::create(CoursesCounterIdMother::random(), CoursesCounterTotalMother::random()); - } -} diff --git a/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterMother.php b/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterMother.php deleted file mode 100644 index fead9021c..000000000 --- a/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterMother.php +++ /dev/null @@ -1,46 +0,0 @@ -id(), - CoursesCounterTotalMother::create($existingCounter->total()->value() + 1), - ...array_merge($existingCounter->existingCourses(), [$courseId]) - ); - } - - public static function random(): CoursesCounter - { - return self::create( - CoursesCounterIdMother::random(), - CoursesCounterTotalMother::random(), - ...Repeater::random(CourseIdMother::creator()) - ); - } -} diff --git a/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php b/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php deleted file mode 100644 index 79520168f..000000000 --- a/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php +++ /dev/null @@ -1,26 +0,0 @@ -service(EntityManager::class)); - - $arranger->arrange(); - } - - protected function tearDown(): void - { - $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); - - $arranger->close(); - - parent::tearDown(); - } - - protected function clearUnitOfWork(): void - { - $this->service(EntityManager::class)->clear(); - } -} diff --git a/tests/src/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php b/tests/src/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php deleted file mode 100644 index 591e94532..000000000 --- a/tests/src/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php +++ /dev/null @@ -1,29 +0,0 @@ -entityManager = $entityManager; - } - - public function arrange(): void - { - apply(new DatabaseCleaner(), [$this->entityManager]); - } - - public function close(): void - { - } -} diff --git a/tests/src/Mooc/Videos/Domain/.gitkeep b/tests/src/Mooc/Videos/Domain/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/src/Mooc/Videos/Infrastructure/.gitkeep b/tests/src/Mooc/Videos/Infrastructure/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/src/Shared/Domain/Criteria/CriteriaMother.php b/tests/src/Shared/Domain/Criteria/CriteriaMother.php deleted file mode 100644 index 8966d67e3..000000000 --- a/tests/src/Shared/Domain/Criteria/CriteriaMother.php +++ /dev/null @@ -1,37 +0,0 @@ -getName()])) { - $property->setAccessible(true); - $property->setValue($duplicated, $newParams[$property->getName()]); - } - }, - $reflection->getProperties() - ); - - return $duplicated; - } -} diff --git a/tests/src/Shared/Domain/IntegerMother.php b/tests/src/Shared/Domain/IntegerMother.php deleted file mode 100644 index 493b50d0c..000000000 --- a/tests/src/Shared/Domain/IntegerMother.php +++ /dev/null @@ -1,28 +0,0 @@ -numberBetween($min, $max); - } - - public static function lessThan($max): int - { - return self::between(1, $max); - } - - public static function moreThan($min): int - { - return self::between($min); - } - - public static function random(): int - { - return self::between(1); - } -} diff --git a/tests/src/Shared/Domain/MotherCreator.php b/tests/src/Shared/Domain/MotherCreator.php deleted file mode 100644 index 2945ce09a..000000000 --- a/tests/src/Shared/Domain/MotherCreator.php +++ /dev/null @@ -1,18 +0,0 @@ -randomElement($elements); - } -} diff --git a/tests/src/Shared/Domain/Repeater.php b/tests/src/Shared/Domain/Repeater.php deleted file mode 100644 index 7224b5dff..000000000 --- a/tests/src/Shared/Domain/Repeater.php +++ /dev/null @@ -1,25 +0,0 @@ -evaluate($actual, '', true); - } - - public static function assertSimilar($expected, $actual): void - { - $constraint = new CodelyTvConstraintIsSimilar($expected); - - $constraint->evaluate($actual); - } - - public static function similarTo($value, $delta = 0.0): CodelyTvMatcherIsSimilar - { - return new CodelyTvMatcherIsSimilar($value, $delta); - } -} diff --git a/tests/src/Shared/Domain/UuidMother.php b/tests/src/Shared/Domain/UuidMother.php deleted file mode 100644 index 4247841a2..000000000 --- a/tests/src/Shared/Domain/UuidMother.php +++ /dev/null @@ -1,13 +0,0 @@ -unique()->uuid; - } -} diff --git a/tests/src/Shared/Domain/WordMother.php b/tests/src/Shared/Domain/WordMother.php deleted file mode 100644 index 6dfaf08c0..000000000 --- a/tests/src/Shared/Domain/WordMother.php +++ /dev/null @@ -1,13 +0,0 @@ -word; - } -} diff --git a/tests/src/Shared/Infrastructure/Behat/ApiContext.php b/tests/src/Shared/Infrastructure/Behat/ApiContext.php deleted file mode 100644 index 564f38ece..000000000 --- a/tests/src/Shared/Infrastructure/Behat/ApiContext.php +++ /dev/null @@ -1,108 +0,0 @@ -minkSession = $minkSession; - $this->sessionHelper = new MinkHelper($this->minkSession); - $this->request = new MinkSessionRequestHelper(new MinkHelper($minkSession)); - } - - /** - * @Given I send a :method request to :url - */ - public function iSendARequestTo($method, $url): void - { - $this->request->sendRequest($method, $this->locatePath($url)); - } - - /** - * @Given I send a :method request to :url with body: - */ - public function iSendARequestToWithBody($method, $url, PyStringNode $body): void - { - $this->request->sendRequestWithPyStringNode($method, $this->locatePath($url), $body); - } - - /** - * @Then the response content should be: - */ - public function theResponseContentShouldBe(PyStringNode $expectedResponse): void - { - $expected = $this->sanitizeOutput($expectedResponse->getRaw()); - $actual = $this->sanitizeOutput($this->sessionHelper->getResponse()); - - if ($expected !== $actual) { - throw new RuntimeException( - sprintf("The outputs does not match!\n\n-- Expected:\n%s\n\n-- Actual:\n%s", $expected, $actual) - ); - } - } - - /** - * @Then the response should be empty - */ - public function theResponseShouldBeEmpty(): void - { - $actual = trim($this->sessionHelper->getResponse()); - - if (!empty($actual)) { - throw new RuntimeException( - sprintf("The outputs is not empty, Actual:\n%s", $actual) - ); - } - } - - /** - * @Then print last api response - */ - public function printApiResponse(): void - { - print_r($this->sessionHelper->getResponse()); - } - - /** - * @Then print response headers - */ - public function printResponseHeaders(): void - { - print_r($this->sessionHelper->getResponseHeaders()); - } - - /** - * @Then the response status code should be :expectedResponseCode - */ - public function theResponseStatusCodeShouldBe($expectedResponseCode): void - { - if ($this->minkSession->getStatusCode() !== (int) $expectedResponseCode) { - throw new RuntimeException( - sprintf( - 'The status code <%s> does not match the expected <%s>', - $this->minkSession->getStatusCode(), - $expectedResponseCode - ) - ); - } - } - - private function sanitizeOutput(string $output) - { - return json_encode(json_decode(trim($output), true)); - } -} diff --git a/tests/src/Shared/Infrastructure/Behat/ApplicationFeatureContext.php b/tests/src/Shared/Infrastructure/Behat/ApplicationFeatureContext.php deleted file mode 100644 index 590dc2a11..000000000 --- a/tests/src/Shared/Infrastructure/Behat/ApplicationFeatureContext.php +++ /dev/null @@ -1,45 +0,0 @@ -connections = $connections; - $this->bus = $bus; - $this->deserializer = $deserializer; - } - - /** @BeforeScenario */ - public function cleanEnvironment(): void - { - $this->connections->clear(); - $this->connections->truncate(); - } - - /** - * @Given /^I send an event to the event bus:$/ - */ - public function iSendAnEventToTheEventBus(PyStringNode $event) - { - $domainEvent = $this->deserializer->deserialize($event->getRaw()); - - $this->bus->publish($domainEvent); - } -} diff --git a/tests/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php b/tests/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php deleted file mode 100644 index 011bd365f..000000000 --- a/tests/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php +++ /dev/null @@ -1,49 +0,0 @@ -commandBus = new InMemorySymfonyCommandBus([$this->commandHandler()]); - } - - /** @test */ - public function it_should_be_able_to_handle_a_command(): void - { - $this->expectException(RuntimeException::class); - - $this->commandBus->dispatch(new FakeCommand()); - } - - /** @test */ - public function it_should_raise_an_exception_dispatching_a_non_registered_command(): void - { - $this->expectException(CommandNotRegisteredError::class); - - $this->commandBus->dispatch($this->mock(Command::class)); - } - - private function commandHandler() - { - return new class { - public function __invoke(FakeCommand $command) - { - throw new RuntimeException('This works fine!'); - } - }; - } -} diff --git a/tests/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php b/tests/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php deleted file mode 100644 index 9ceae4507..000000000 --- a/tests/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php +++ /dev/null @@ -1,52 +0,0 @@ -bus = new MySqlDoctrineEventBus($this->service(EntityManager::class)); - $this->consumer = new MySqlDoctrineDomainEventsConsumer( - $this->service(EntityManager::class), - $this->service(DomainEventMapping::class) - ); - } - - /** @test */ - public function it_should_publish_and_consume_domain_events_from_msql(): void - { - $domainEvent = CourseCreatedDomainEventMother::random(); - $anotherDomainEvent = CoursesCounterIncrementedDomainEventMother::random(); - - $this->bus->publish($domainEvent, $anotherDomainEvent); - - $this->consumer->consume( - $this->spySubscriber($domainEvent, $anotherDomainEvent), - $eventsToConsume = 2 - ); - } - - private function spySubscriber(DomainEvent ...$expectedDomainEvents): callable - { - return function (DomainEvent $domainEvent) use ($expectedDomainEvents): void { - $this->assertContainsEquals($domainEvent, $expectedDomainEvents); - }; - } -} diff --git a/tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php b/tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php deleted file mode 100644 index 1fde6cdd8..000000000 --- a/tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php +++ /dev/null @@ -1,178 +0,0 @@ -connection = $this->service(RabbitMqConnection::class); - - $this->exchangeName = 'test_domain_events'; - $this->configurer = new RabbitMqConfigurer($this->connection); - $this->publisher = new RabbitMqEventBus( - $this->connection, - $this->exchangeName, - $this->service(MySqlDoctrineEventBus::class) - ); - $this->consumer = new RabbitMqDomainEventsConsumer( - $this->connection, - $this->service(DomainEventJsonDeserializer::class), - $this->exchangeName, - $maxRetries = 1 - ); - $this->fakeSubscriber = new TestAllWorksOnRabbitMqEventsPublished(); - $this->consumerHasBeenExecuted = false; - - $this->cleanEnvironment($this->connection); - } - - /** @test */ - public function it_should_publish_and_consume_domain_events_from_rabbitmq(): void - { - $domainEvent = CourseCreatedDomainEventMother::random(); - - $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); - - $this->publisher->publish($domainEvent); - - $this->consumer->consume( - $this->assertConsumer($domainEvent), - RabbitMqQueueNameFormatter::format($this->fakeSubscriber) - ); - - $this->assertTrue($this->consumerHasBeenExecuted); - } - - /** @test */ - public function it_should_throw_an_exception_consuming_non_existing_domain_events(): void - { - $this->expectException(RuntimeException::class); - - $domainEvent = CoursesCounterIncrementedDomainEventMother::random(); - - $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); - - $this->publisher->publish($domainEvent); - - $this->consumer->consume( - $this->assertConsumer($domainEvent), - RabbitMqQueueNameFormatter::format($this->fakeSubscriber) - ); - - $this->assertTrue($this->consumerHasBeenExecuted); - } - - /** @test */ - public function it_should_retry_failed_domain_events(): void - { - $domainEvent = CourseCreatedDomainEventMother::random(); - - $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); - - $this->publisher->publish($domainEvent); - - $this->simulateErrorConsuming(); - - sleep(1); - - $this->consumer->consume( - $this->assertConsumer($domainEvent), - RabbitMqQueueNameFormatter::format($this->fakeSubscriber) - ); - - $this->assertTrue($this->consumerHasBeenExecuted); - } - - /** @test */ - public function it_should_send_events_to_dead_letter_after_retry_failed_domain_events(): void - { - $domainEvent = CourseCreatedDomainEventMother::random(); - - $this->configurer->configure($this->exchangeName, $this->fakeSubscriber); - - $this->publisher->publish($domainEvent); - - $this->simulateErrorConsuming(); - - sleep(1); - - $this->simulateErrorConsuming(); - - $this->assertDeadLetterContainsEvent(1); - } - - private function assertConsumer(DomainEvent ...$expectedDomainEvents): callable - { - return function (DomainEvent $domainEvent) use ($expectedDomainEvents): void { - $this->assertContainsEquals($domainEvent, $expectedDomainEvents); - - $this->consumerHasBeenExecuted = true; - }; - } - - private function failingConsumer(): callable - { - return static function (DomainEvent $domainEvent): void { - throw new RuntimeException('To test'); - }; - } - - private function simulateErrorConsuming(): void - { - try { - $this->consumer->consume( - $this->failingConsumer(), - RabbitMqQueueNameFormatter::format($this->fakeSubscriber) - ); - } catch (Throwable $error) { - $this->assertInstanceOf(RuntimeException::class, $error); - } - } - - private function cleanEnvironment(RabbitMqConnection $connection): void - { - $connection->queue(RabbitMqQueueNameFormatter::format($this->fakeSubscriber))->delete(); - $connection->queue(RabbitMqQueueNameFormatter::formatRetry($this->fakeSubscriber))->delete(); - $connection->queue(RabbitMqQueueNameFormatter::formatDeadLetter($this->fakeSubscriber))->delete(); - } - - private function assertDeadLetterContainsEvent(int $expectedNumberOfEvents): void - { - $totalEventsInDeadLetter = 0; - - while ($this->connection->queue(RabbitMqQueueNameFormatter::formatDeadLetter($this->fakeSubscriber))->get( - AMQP_AUTOACK - )) { - $totalEventsInDeadLetter++; - } - - $this->assertSame($expectedNumberOfEvents, $totalEventsInDeadLetter); - } -} diff --git a/tests/src/Shared/Infrastructure/Bus/Query/FakeResponse.php b/tests/src/Shared/Infrastructure/Bus/Query/FakeResponse.php deleted file mode 100644 index 034d3e63b..000000000 --- a/tests/src/Shared/Infrastructure/Bus/Query/FakeResponse.php +++ /dev/null @@ -1,22 +0,0 @@ -number = $number; - } - - public function number(): int - { - return $this->number; - } -} diff --git a/tests/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php b/tests/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php deleted file mode 100644 index 141dde880..000000000 --- a/tests/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php +++ /dev/null @@ -1,49 +0,0 @@ -queryBus = new InMemorySymfonyQueryBus([$this->queryHandler()]); - } - - /** @test */ - public function it_should_return_a_response_successfully(): void - { - $this->expectException(RuntimeException::class); - - $this->queryBus->ask(new FakeQuery()); - } - - /** @test */ - public function it_should_raise_an_exception_dispatching_a_non_registered_query(): void - { - $this->expectException(QueryNotRegisteredError::class); - - $this->queryBus->ask($this->mock(Query::class)); - } - - private function queryHandler() - { - return new class { - public function __invoke(FakeQuery $query) - { - throw new RuntimeException('This works fine!'); - } - }; - } -} diff --git a/tests/src/Shared/Infrastructure/Doctrine/DatabaseCleaner.php b/tests/src/Shared/Infrastructure/Doctrine/DatabaseCleaner.php deleted file mode 100644 index ccd9caffe..000000000 --- a/tests/src/Shared/Infrastructure/Doctrine/DatabaseCleaner.php +++ /dev/null @@ -1,40 +0,0 @@ -getConnection(); - - $tables = $this->tables($connection); - $truncateTablesSql = $this->truncateDatabaseSql($tables); - - $connection->exec($truncateTablesSql); - } - - private function truncateDatabaseSql(array $tables): string - { - $truncateTables = map($this->truncateTableSql(), $tables); - - return sprintf('SET FOREIGN_KEY_CHECKS = 0; %s SET FOREIGN_KEY_CHECKS = 1;', implode(' ', $truncateTables)); - } - - private function truncateTableSql(): callable - { - return fn(array $table): string => sprintf('TRUNCATE TABLE `%s`;', first($table)); - } - - private function tables(Connection $connection): array - { - return $connection->query('SHOW TABLES')->fetchAll(); - } -} diff --git a/tests/src/Shared/Infrastructure/Mink/MinkHelper.php b/tests/src/Shared/Infrastructure/Mink/MinkHelper.php deleted file mode 100644 index cae4f37e3..000000000 --- a/tests/src/Shared/Infrastructure/Mink/MinkHelper.php +++ /dev/null @@ -1,95 +0,0 @@ -session = $session; - } - - public function sendRequest($method, $url, array $optionalParams = []): Crawler - { - $defaultOptionalParams = [ - 'parameters' => [], - 'files' => [], - 'server' => ['HTTP_ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json'], - 'content' => null, - 'changeHistory' => true, - ]; - - $optionalParams = array_merge($defaultOptionalParams, $optionalParams); - - $crawler = $this->getClient()->request( - $method, - $url, - $optionalParams['parameters'], - $optionalParams['files'], - $optionalParams['server'], - $optionalParams['content'], - $optionalParams['changeHistory'] - ); - - $this->resetRequestStuff(); - - return $crawler; - } - - public function getResponse(): string - { - return $this->getSession()->getPage()->getContent(); - } - - public function getResponseHeaders(): array - { - return $this->normalizeHeaders( - array_change_key_case($this->getSession()->getResponseHeaders(), CASE_LOWER) - ); - } - - public function resetServerParameters(): void - { - $this->getClient()->setServerParameters([]); - } - - public function getRequest(): object - { - return $this->getClient()->getRequest(); - } - - private function getSession(): Session - { - return $this->session; - } - - private function getDriver(): DriverInterface - { - return $this->getSession()->getDriver(); - } - - private function getClient(): AbstractBrowser - { - return $this->getDriver()->getClient(); - } - - private function normalizeHeaders(array $headers): array - { - return array_map('implode', array_filter($headers)); - } - - private function resetRequestStuff(): void - { - $this->getSession()->reset(); - $this->resetServerParameters(); - } -} diff --git a/tests/src/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php b/tests/src/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php deleted file mode 100644 index 63fdd6561..000000000 --- a/tests/src/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php +++ /dev/null @@ -1,34 +0,0 @@ -sessionHelper = $sessionHelper; - } - - public function sendRequest($method, $url, array $optionalParams = []): void - { - $this->request($method, $url, $optionalParams); - } - - public function sendRequestWithPyStringNode($method, $url, PyStringNode $body): void - { - $this->request($method, $url, ['content' => $body->getRaw()]); - } - - public function request($method, $url, array $optionalParams = []): Crawler - { - return $this->sessionHelper->sendRequest($method, $url, $optionalParams); - } -} diff --git a/tests/src/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php b/tests/src/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php deleted file mode 100644 index af785a3e0..000000000 --- a/tests/src/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php +++ /dev/null @@ -1,30 +0,0 @@ -constraint = new CodelyTvConstraintIsSimilar($value, $delta); - } - - public function match(&$actual): bool - { - return $this->constraint->evaluate($actual, '', true); - } - - public function __toString(): string - { - return 'Is similar'; - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php b/tests/src/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php deleted file mode 100644 index d813d5251..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php +++ /dev/null @@ -1,48 +0,0 @@ -contains($expected, $actual) || count($expected) !== count($actual)) { - throw new ComparisonFailure( - $expected, - $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting the collection of AGs contains all the expected elements.' - ); - } - } - - private function contains(array $expectedArray, array $actualArray): bool - { - $exists = fn(AggregateRoot $expected) => any( - fn(AggregateRoot $actual) => TestUtils::isSimilar($expected, $actual), - $actualArray - ); - - return all($exists, $expectedArray); - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php b/tests/src/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php deleted file mode 100644 index b52478a5f..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php +++ /dev/null @@ -1,78 +0,0 @@ -pullDomainEvents(); - - if (!$this->aggregateRootsAreSimilar($expected, $actualEntity)) { - throw new ComparisonFailure( - $expected, - $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting the aggregate roots are equal.' - ); - } - } - - private function aggregateRootsAreSimilar(AggregateRoot $expected, AggregateRoot $actual): bool - { - if (!$this->aggregateRootsAreTheSameClass($expected, $actual)) { - return false; - } - - return $this->aggregateRootPropertiesAreSimilar($expected, $actual); - } - - private function aggregateRootsAreTheSameClass(AggregateRoot $expected, AggregateRoot $actual): bool - { - return get_class($expected) === get_class($actual); - } - - private function aggregateRootPropertiesAreSimilar(AggregateRoot $expected, AggregateRoot $actual): bool - { - $expectedReflected = new ReflectionObject($expected); - $actualReflected = new ReflectionObject($actual); - - foreach ($expectedReflected->getProperties() as $expectedReflectedProperty) { - $actualReflectedProperty = $actualReflected->getProperty($expectedReflectedProperty->getName()); - - $expectedReflectedProperty->setAccessible(true); - $actualReflectedProperty->setAccessible(true); - - $expectedProperty = $expectedReflectedProperty->getValue($expected); - $actualProperty = $actualReflectedProperty->getValue($actual); - - if (!TestUtils::isSimilar($expectedProperty, $actualProperty)) { - return false; - } - } - - return true; - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php b/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php deleted file mode 100644 index 38d75e662..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php +++ /dev/null @@ -1,50 +0,0 @@ -sub($intervalWithDelta) || $actual > $expectedUpper->add($intervalWithDelta)) { - throw new ComparisonFailure( - $expected, - $actual, - $this->dateTimeToString($expected), - $this->dateTimeToString($actual), - false, - 'Failed asserting that two DateTime objects are equal.' - ); - } - } - - protected function dateTimeToString(DateTimeInterface $datetime): string - { - return $datetime->format(DateTime::ATOM) ?: 'Invalid DateTime object'; - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php b/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php deleted file mode 100644 index 880c51ed5..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php +++ /dev/null @@ -1,72 +0,0 @@ -isValidDateTimeString($expected) && - $this->isValidDateTimeString($actual); - } - - public function assertEquals( - $expected, - $actual, - $delta = 0.0, - $canonicalize = false, - $ignoreCase = false, - array &$processed = [] - ): void { - $expectedDate = new DateTimeImmutable($expected); - $actualDate = new DateTimeImmutable($actual); - - $normalizedDelta = $delta === 0.0 ? 10 : $delta; - $intervalWithDelta = new DateInterval(sprintf('PT%sS', abs($normalizedDelta))); - - if ($actualDate < $expectedDate->sub($intervalWithDelta) || - $actualDate > $expectedDate->add($intervalWithDelta)) { - throw new ComparisonFailure( - $expectedDate, - $actualDate, - $this->dateTimeToString($expectedDate), - $this->dateTimeToString($actualDate), - false, - 'Failed asserting that two DateTime strings are equal.' - ); - } - } - - protected function dateTimeToString(DateTimeInterface $datetime): string - { - $string = $datetime->format(DateTime::ATOM); - - return $string ?: 'Invalid DateTime object'; - } - - private function isValidDateTimeString($expected): bool - { - $isValid = true; - - try { - new DateTimeImmutable($expected); - } catch (Throwable $throwable) { - $isValid = false; - } - - return $isValid; - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php b/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php deleted file mode 100644 index d14af789c..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php +++ /dev/null @@ -1,48 +0,0 @@ -contains($expected, $actual) || count($expected) !== count($actual)) { - throw new ComparisonFailure( - $expected, - $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting the collection of Events contains all the expected elements.' - ); - } - } - - private function contains(array $expectedArray, array $actualArray): bool - { - $exists = static fn(DomainEvent $expected) => any( - static fn(DomainEvent $actual) => TestUtils::isSimilar($expected, $actual), - $actualArray - ); - - return all($exists, $expectedArray); - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php b/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php deleted file mode 100644 index c76301430..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php +++ /dev/null @@ -1,79 +0,0 @@ -areSimilar($expected, $actual)) { - throw new ComparisonFailure( - $expected, - $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting the events are equal.' - ); - } - } - - private function areSimilar(DomainEvent $expected, DomainEvent $actual): bool - { - if (!$this->areTheSameClass($expected, $actual)) { - return false; - } - - return $this->propertiesAreSimilar($expected, $actual); - } - - private function areTheSameClass(DomainEvent $expected, DomainEvent $actual): bool - { - return get_class($expected) === get_class($actual); - } - - private function propertiesAreSimilar(DomainEvent $expected, DomainEvent $actual): bool - { - $expectedReflected = new ReflectionObject($expected); - $actualReflected = new ReflectionObject($actual); - - foreach ($expectedReflected->getProperties() as $expectedReflectedProperty) { - if (!in_array($expectedReflectedProperty->getName(), self::$ignoredAttributes, false)) { - $actualReflectedProperty = $actualReflected->getProperty($expectedReflectedProperty->getName()); - - $expectedReflectedProperty->setAccessible(true); - $actualReflectedProperty->setAccessible(true); - - $expectedProperty = $expectedReflectedProperty->getValue($expected); - $actualProperty = $actualReflectedProperty->getValue($actual); - - if (!TestUtils::isSimilar($expectedProperty, $actualProperty)) { - return false; - } - } - } - - return true; - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php b/tests/src/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php deleted file mode 100644 index 37b5c1f2b..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php +++ /dev/null @@ -1,95 +0,0 @@ -value = $value; - $this->delta = $delta; - } - - public function evaluate($other, $description = '', $returnResult = false): bool - { - if ($this->value === $other) { - return true; - } - - $isValid = true; - $comparatorFactory = new Factory(); - - $comparatorFactory->register(new AggregateRootArraySimilarComparator()); - $comparatorFactory->register(new AggregateRootSimilarComparator()); - $comparatorFactory->register(new DomainEventArraySimilarComparator()); - $comparatorFactory->register(new DomainEventSimilarComparator()); - $comparatorFactory->register(new DateTimeSimilarComparator()); - $comparatorFactory->register(new DateTimeStringSimilarComparator()); - - try { - $comparator = $comparatorFactory->getComparatorFor($other, $this->value); - - $comparator->assertEquals($this->value, $other, $this->delta); - } catch (ComparisonFailure $f) { - if (!$returnResult) { - throw new ExpectationFailedException( - trim($description . "\n" . $f->getMessage()), - $f - ); - } - - $isValid = false; - } - - return $isValid; - } - - public function toString(): string - { - $delta = ''; - - if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { - return 'is equal to '; - } - - return sprintf( - "is equal to '%s'", - $this->value - ); - } - - if ($this->delta !== 0) { - $delta = sprintf( - ' with delta <%F>', - $this->delta - ); - } - - return sprintf( - 'is equal to %s%s', - $this->exporter()->export($this->value), - $delta - ); - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php b/tests/src/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php deleted file mode 100644 index 2d96e8f67..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php +++ /dev/null @@ -1,42 +0,0 @@ - 'test']); - - parent::setUp(); - - // @todo This should be for the "Shared Infrastructure" connection - $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); - - $arranger->arrange(); - } - - protected function assertSimilar($expected, $actual): void - { - TestUtils::assertSimilar($expected, $actual); - } - - /** @return mixed */ - protected function service($id) - { - return self::$container->get($id); - } - - /** @return mixed */ - protected function parameter($parameter) - { - return self::$container->getParameter($parameter); - } -} diff --git a/tests/src/Shared/Infrastructure/PhpUnit/UnitTestCase.php b/tests/src/Shared/Infrastructure/PhpUnit/UnitTestCase.php deleted file mode 100644 index 2740ea652..000000000 --- a/tests/src/Shared/Infrastructure/PhpUnit/UnitTestCase.php +++ /dev/null @@ -1,104 +0,0 @@ -eventBus() - ->shouldReceive('publish') - ->with($this->similarTo($domainEvent)) - ->andReturnNull(); - } - - protected function shouldNotPublishDomainEvent(): void - { - $this->eventBus() - ->shouldReceive('publish') - ->withNoArgs() - ->andReturnNull(); - } - - /** @return EventBus|MockInterface */ - protected function eventBus(): MockInterface - { - return $this->eventBus = $this->eventBus ?: $this->mock(EventBus::class); - } - - protected function shouldGenerateUuid(string $uuid): void - { - $this->uuidGenerator() - ->shouldReceive('generate') - ->once() - ->withNoArgs() - ->andReturn($uuid); - } - - /** @return UuidGenerator|MockInterface */ - protected function uuidGenerator(): MockInterface - { - return $this->uuidGenerator = $this->uuidGenerator ?: $this->mock(UuidGenerator::class); - } - - protected function notify(DomainEvent $event, callable $subscriber): void - { - $subscriber($event); - } - - protected function dispatch(Command $command, callable $commandHandler): void - { - $commandHandler($command); - } - - protected function assertAskResponse(Response $expected, Query $query, callable $queryHandler): void - { - $actual = $queryHandler($query); - - $this->assertEquals($expected, $actual); - } - - protected function assertAskThrowsException(string $expectedErrorClass, Query $query, callable $queryHandler): void - { - $this->expectException($expectedErrorClass); - - $queryHandler($query); - } - - protected function isSimilar($expected, $actual): bool - { - return TestUtils::isSimilar($expected, $actual); - } - - protected function assertSimilar($expected, $actual): void - { - TestUtils::assertSimilar($expected, $actual); - } - - protected function similarTo($value, $delta = 0.0): MatcherAbstract - { - return TestUtils::similarTo($value, $delta); - } -}