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 5ff07378c..e99bf3b81 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -7,7 +7,7 @@ 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' @@ -15,3 +15,4 @@ jobs: 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 f4ece3da8..efec06c20 100644 --- a/Makefile +++ b/Makefile @@ -1,67 +1,54 @@ -.PHONY: build deps composer-install composer-update composer reload test run-tests start stop destroy doco rebuild start-local ping-mysql composer-require composer-require-module - current-dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -build: deps start - -deps: composer-install - -# 🐘 Composer -composer-env-file: - @if [ ! -f .env.local ]; then echo '' > .env.local; fi - -composer-install: CMD=install -composer-update: CMD=update -composer-require: CMD=require -composer-require: INTERACTIVE=-ti --interactive -composer-require-module: CMD=require $(module) -composer-require-module: INTERACTIVE=-ti --interactive -composer composer-install composer-update composer-require composer-require-module: composer-env-file +composer-install: @docker run --rm $(INTERACTIVE) --volume $(current-dir):/app --user $(id -u):$(id -g) \ - clevyr/prestissimo $(CMD) \ + composer:2.6.4 install \ --ignore-platform-reqs \ --no-ansi -reload: composer-env-file - @docker-compose exec php-fpm kill -USR2 1 - @docker-compose exec nginx nginx -s reload +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 -test: composer-env-file - @docker exec codelytv-php_ddd_skeleton-php make run-tests +static-analysis: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/psalm --output-format=github --shepherd -run-tests: composer-env-file - 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 +lint: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/ecs check -# 🐳 Docker Compose -start: CMD=up -d -stop: CMD=stop -destroy: CMD=down +test-architecture: + docker exec codely-php_ddd_skeleton-mooc_backend-php php -d memory_limit=4G ./vendor/bin/phpstan analyse --error-format=github -# Usage: `make doco CMD="ps --services"` -# Usage: `make doco CMD="build --parallel --pull --force-rm --no-cache"` -doco start stop destroy: composer-env-file - @docker-compose $(CMD) +mess-detector: + docker exec codely-php_ddd_skeleton-mooc_backend-php ./vendor/bin/phpmd apps,src,tests github phpmd.xml -rebuild: composer-env-file - docker-compose build --pull --force-rm --no-cache - make deps - make start +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 + +stop: + UID=${shell id -u} GID=${shell id -g} docker compose stop -prepare-local: - curl -sS https://get.symfony.com/cli/installer | bash +destroy: + UID=${shell id -u} GID=${shell id -g} docker compose down -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 +rebuild: + docker compose build --pull --force-rm --no-cache + make install + make start -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 + @docker exec codely-php_ddd_skeleton-mooc-mysql mysqladmin --user=root --password= --host "127.0.0.1" ping --silent + +ping-elasticsearch: + @curl -I -XHEAD localhost:9200 + +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 7f02db2be..d0e25db62 100644 --- a/apps/backoffice/backend/config/services.yaml +++ b/apps/backoffice/backend/config/services.yaml @@ -88,6 +88,11 @@ services: - '%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/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 7642e5e76..321bf5524 100644 --- a/apps/backoffice/backend/src/BackofficeBackendKernel.php +++ b/apps/backoffice/backend/src/BackofficeBackendKernel.php @@ -9,45 +9,38 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; -use Symfony\Component\Routing\RouteCollectionBuilder; + use function dirname; -final class BackofficeBackendKernel extends Kernel +class BackofficeBackendKernel extends Kernel { - use MicroKernelTrait; - - private const 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'); - } - - 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 bbd468882..4fcf89a26 100644 --- a/apps/backoffice/backend/src/Controller/Courses/CoursesGetController.php +++ b/apps/backoffice/backend/src/Controller/Courses/CoursesGetController.php @@ -10,41 +10,42 @@ use CodelyTv\Shared\Domain\Bus\Query\QueryBus; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; + use function Lambdish\Phunctional\map; -final class CoursesGetController +final readonly class CoursesGetController { - private $queryBus; - - public function __construct(QueryBus $queryBus) - { - $this->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( - fn(BackofficeCourseResponse $course) => [ - 'id' => $course->id(), - 'name' => $course->name(), - 'duration' => $course->duration(), - ], - $response->courses() - ), - 200, - ['Access-Control-Allow-Origin' => '*'] - ); - } + 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 af32d30ea..fe2256009 100644 --- a/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php +++ b/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php @@ -9,12 +9,12 @@ final class HealthCheckGetController { - public function __invoke(Request $request): JsonResponse - { - return new JsonResponse( - [ - 'backoffice-backend' => '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 b1ef9c69f..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 @@ -88,8 +92,11 @@ services: - '%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/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 f0658663a..ae109b24a 100644 --- a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php +++ b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php @@ -9,45 +9,38 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; -use Symfony\Component\Routing\RouteCollectionBuilder; + use function dirname; -final class BackofficeFrontendKernel extends Kernel +class BackofficeFrontendKernel extends Kernel { - use MicroKernelTrait; - - private const 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'); - } - - 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 0edfe8692..c27dd2396 100644 --- a/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php +++ b/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php @@ -12,25 +12,21 @@ final class ImportCoursesToElasticsearchCommand extends Command { - private MySqlBackofficeCourseRepository $mySqlRepository; - private ElasticsearchBackofficeCourseRepository $elasticRepository; - - public function __construct( - MySqlBackofficeCourseRepository $mySqlRepository, - ElasticsearchBackofficeCourseRepository $elasticRepository - ) { - parent::__construct(); - - $this->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 6bf2d2335..a9604d1b2 100644 --- a/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php +++ b/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php @@ -6,31 +6,31 @@ use CodelyTv\Mooc\CoursesCounter\Application\Find\CoursesCounterResponse; use CodelyTv\Mooc\CoursesCounter\Application\Find\FindCoursesCounterQuery; -use CodelyTv\Shared\Domain\ValueObject\Uuid; +use CodelyTv\Shared\Domain\ValueObject\SimpleUuid; use CodelyTv\Shared\Infrastructure\Symfony\WebController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; final class CoursesGetWebController extends WebController { - protected function exceptions(): array - { - return []; - } + public function __invoke(Request $request): Response + { + /** @var CoursesCounterResponse $coursesCounterResponse */ + $coursesCounterResponse = $this->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 48757a4b8..625d2a1d0 100644 --- a/apps/backoffice/frontend/src/Controller/Courses/CoursesPostWebController.php +++ b/apps/backoffice/frontend/src/Controller/Courses/CoursesPostWebController.php @@ -14,48 +14,48 @@ final class CoursesPostWebController extends WebController { - protected function exceptions(): array - { - return []; - } - - public function __invoke(Request $request): RedirectResponse - { - $validationErrors = $this->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 19c48acb4..a38a47d39 100644 --- a/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php +++ b/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php @@ -9,12 +9,12 @@ final class HealthCheckGetController { - public function __invoke(Request $request): JsonResponse - { - return new JsonResponse( - [ - 'backoffice-frontend' => '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 7470e76c8..30e011847 100644 --- a/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php +++ b/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php @@ -10,19 +10,16 @@ final class HomeGetWebController extends WebController { - protected function exceptions(): array - { - return []; - } + public function __invoke(Request $request): Response + { + return $this->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/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 152e1e7c8..16d96c2a6 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php @@ -8,55 +8,49 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator; use CodelyTv\Shared\Infrastructure\Bus\Event\MySql\MySqlDoctrineDomainEventsConsumer; use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; + use function Lambdish\Phunctional\pipe; +#[AsCommand(name: 'codely:domain-events:mysql:consume', description: 'Consume domain events from MySql',)] final class ConsumeMySqlDomainEventsCommand extends Command { - protected static $defaultName = 'codelytv:domain-events:mysql:consume'; - private MySqlDoctrineDomainEventsConsumer $consumer; - private DomainEventSubscriberLocator $subscriberLocator; - private DatabaseConnections $connections; - - public function __construct( - MySqlDoctrineDomainEventsConsumer $consumer, - DatabaseConnections $connections, - DomainEventSubscriberLocator $subscriberLocator - ) { - $this->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(), fn() => $this->connections->clear()); - - $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); - } - }; - } + 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 675dfed6b..72801af71 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php @@ -5,34 +5,30 @@ namespace CodelyTv\Apps\Mooc\Backend\Command\DomainEvents\RabbitMq; use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqConfigurer; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Traversable; +#[AsCommand( + name: 'codely:domain-events:rabbitmq:configure', + description: 'Configure the RabbitMQ to allow publish & consume domain events', +)] final class ConfigureRabbitMqCommand extends Command { - protected static $defaultName = 'codelytv:domain-events:rabbitmq:configure'; - private RabbitMqConfigurer $configurer; - private string $exchangeName; - private Traversable $subscribers; - - public function __construct(RabbitMqConfigurer $configurer, string $exchangeName, Traversable $subscribers) - { - parent::__construct(); - - $this->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 f2f2f8105..c29c056d8 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php @@ -7,55 +7,53 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator; use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqDomainEventsConsumer; use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; + use function Lambdish\Phunctional\repeat; +#[AsCommand( + name: 'codely:domain-events:rabbitmq:consume', + description: 'Consume domain events from the RabbitMQ', +)] final class ConsumeRabbitMqDomainEventsCommand extends Command { - protected static $defaultName = 'codelytv:domain-events:rabbitmq:consume'; - private RabbitMqDomainEventsConsumer $consumer; - private DatabaseConnections $connections; - private DomainEventSubscriberLocator $locator; - - public function __construct( - RabbitMqDomainEventsConsumer $consumer, - DatabaseConnections $connections, - DomainEventSubscriberLocator $locator - ) { - parent::__construct(); - - $this->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 47d44df47..646392bf3 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php @@ -7,86 +7,82 @@ use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator; use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqQueueNameFormatter; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; + use function Lambdish\Phunctional\each; +#[AsCommand( + name: 'codely:domain-events:rabbitmq:generate-supervisor-files', + description: 'Generate the supervisor configuration for every RabbitMQ subscriber', +)] final class GenerateSupervisorRabbitMqConsumerFilesCommand extends Command { - private const EVENTS_TO_PROCESS_AT_TIME = 200; - private const NUMBERS_OF_PROCESSES_PER_SUBSCRIBER = 1; - private const SUPERVISOR_PATH = __DIR__ . '/../../../../build/supervisor'; - protected static $defaultName = 'codelytv:domain-events:rabbitmq:generate-supervisor-files'; - private DomainEventSubscriberLocator $locator; + private const EVENTS_TO_PROCESS_AT_TIME = 200; + private const NUMBERS_OF_PROCESSES_PER_SUBSCRIBER = 1; + private const SUPERVISOR_PATH = __DIR__ . '/../../../../build/supervisor'; - public function __construct(DomainEventSubscriberLocator $locator) - { - parent::__construct(); + public function __construct(private readonly DomainEventSubscriberLocator $locator) + { + parent::__construct(); + } - $this->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, - $request->request->get('name'), - $request->request->get('duration') - ) - ); + public function __invoke(string $id, Request $request): Response + { + $this->dispatch( + new CreateCourseCommand( + $id, + (string) $request->request->get('name'), + (string) $request->request->get('duration') + ) + ); - return new Response('', Response::HTTP_CREATED); - } + return new Response('', Response::HTTP_CREATED); + } - protected function exceptions(): array - { - return []; - } + 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 13d7ba3ad..0fb94b6ff 100644 --- a/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php +++ b/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php @@ -13,22 +13,22 @@ final class CoursesCounterGetController extends ApiController { - public function __invoke() - { - /** @var CoursesCounterResponse $response */ - $response = $this->ask(new FindCoursesCounterQuery()); + public function __invoke(): JsonResponse + { + /** @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, - ]; - } + 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 39d283f7c..1c20feae2 100644 --- a/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php +++ b/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php @@ -8,22 +8,17 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -final class HealthCheckGetController +final readonly class HealthCheckGetController { - private RandomNumberGenerator $generator; + public function __construct(private RandomNumberGenerator $generator) {} - public function __construct(RandomNumberGenerator $generator) - { - $this->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 c84f3ba39..e0b82d39e 100644 --- a/apps/mooc/backend/src/MoocBackendKernel.php +++ b/apps/mooc/backend/src/MoocBackendKernel.php @@ -9,45 +9,38 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; -use Symfony\Component\Routing\RouteCollectionBuilder; + use function dirname; -final class MoocBackendKernel extends Kernel +class MoocBackendKernel extends Kernel { - use MicroKernelTrait; - - private const 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'); - } - - 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/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/apps/mooc/frontend/var/.gitkeep b/apps/mooc/frontend/var/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/composer.json b/composer.json index 48ef5b128..52f696576 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "project", "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": "*", @@ -12,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.0", + "lambdish/phunctional": "^2", - "ramsey/uuid": "^3.8", + "ramsey/uuid": "^4", - "doctrine/dbal": "^2.9", - "doctrine/orm": "^2.6", - "ocramius/proxy-manager": "^2.6", + "doctrine/dbal": "^3", + "doctrine/orm": "^2", - "elasticsearch/elasticsearch": "^7.3", - "monolog/monolog": "^2.0" + "ocramius/proxy-manager": "^2", + "laminas/laminas-zendframework-bridge": "^1", + + "elasticsearch/elasticsearch": "^7", + "monolog/monolog": "^3", + + "promphp/prometheus_client_php": "^2.7.2" }, "require-dev": { "ext-xdebug": "*", "roave/security-advisories": "dev-master", - "behat/behat": "^3.6", - "friends-of-behat/mink-extension": "^2.4", - "behat/mink-browserkit-driver": "^1.3", - "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": "^9.1", - "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": { @@ -63,5 +79,10 @@ "CodelyTv\\Tests\\": ["tests"] } }, - "minimum-stability": "stable" + "minimum-stability": "RC", + "config": { + "allow-plugins": { + "ocramius/package-versions": true + } + } } diff --git a/composer.lock b/composer.lock index bda815f9a..534d648f6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,89 +4,80 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "36d016d3964f43eaae650522e3866b52", + "content-hash": "1384ca0a67984f7a0296f15a4373fed1", "packages": [ { - "name": "doctrine/annotations", - "version": "1.10.3", + "name": "brick/math", + "version": "0.12.1", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", - "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0" + "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.9.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": "2020-05-25T17:24:27+00:00" + "time": "2023-11-29T23:19:16+00:00" }, { "name": "doctrine/cache", - "version": "1.10.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "35a4a70cd94e09e2259dfae7488afc6b474ecbd3" + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/35a4a70cd94e09e2259dfae7488afc6b474ecbd3", - "reference": "35a4a70cd94e09e2259dfae7488afc6b474ecbd3", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", "shasum": "" }, "require": { @@ -96,21 +87,14 @@ "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" @@ -155,6 +139,10 @@ "redis", "xcache" ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.2.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -169,35 +157,38 @@ "type": "tidelift" } ], - "time": "2020-05-27T16:24:54+00:00" + "time": "2022-05-20T20:07:39+00:00" }, { "name": "doctrine/collections", - "version": "1.6.5", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "fc0206348e17e530d09463fef07ba8968406cd6d" + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/fc0206348e17e530d09463fef07ba8968406cd6d", - "reference": "fc0206348e17e530d09463fef07ba8968406cd6d", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d8af7f248c74f195f7347424600fd9e17b57af59", + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59", "shasum": "" }, "require": { - "php": "^7.1.3 || ^8.0" + "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.8.1" + "doctrine/coding-standard": "^12", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.11" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + "Doctrine\\Common\\Collections\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -234,6 +225,10 @@ "iterators", "php" ], + "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", @@ -248,43 +243,40 @@ "type": "tidelift" } ], - "time": "2020-05-25T19:24:35+00:00" + "time": "2024-04-18T06:56:21+00:00" }, { "name": "doctrine/common", - "version": "3.0.1", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "402a424f1e7dc39ce8fa7901e56d25835978387c" + "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/402a424f1e7dc39ce8fa7901e56d25835978387c", - "reference": "402a424f1e7dc39ce8fa7901e56d25835978387c", + "url": "https://api.github.com/repos/doctrine/common/zipball/0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", + "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", "shasum": "" }, "require": { - "doctrine/persistence": "^2.0", + "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": "3.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -317,13 +309,17 @@ "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" ], + "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", @@ -338,36 +334,44 @@ "type": "tidelift" } ], - "time": "2020-05-30T15:08:34+00:00" + "time": "2024-04-16T13:35:33+00:00" }, { "name": "doctrine/dbal", - "version": "2.10.2", + "version": "3.8.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8" + "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/aab745e7b6b2de3b47019da81e7225e14dcfdac8", - "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8", + "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", - "nikic/php-parser": "^4.4", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^8.4.1", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "^3.11" + "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." @@ -376,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/", @@ -427,11 +425,14 @@ "queryobject", "sasql", "sql", - "sqlanywhere", "sqlite", "sqlserver", "sqlsrv" ], + "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", @@ -446,41 +447,85 @@ "type": "tidelift" } ], - "time": "2020-04-20T17:19:26+00:00" + "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/", @@ -522,41 +567,54 @@ "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.4.3", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c" + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/4650c8b30c753a76bf44fb2ed00117d6f367490c", - "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^7.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "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": "2.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, @@ -600,6 +658,10 @@ "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", @@ -614,40 +676,36 @@ "type": "tidelift" } ], - "time": "2020-05-29T07:19:59+00:00" + "time": "2024-02-18T20:23:39+00:00" }, { "name": "doctrine/instantiator", - "version": "1.3.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "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/" @@ -661,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", @@ -670,6 +728,10 @@ "constructor", "instantiate" ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -684,39 +746,36 @@ "type": "tidelift" } ], - "time": "2020-05-29T17:27:14+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "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/", @@ -746,6 +805,10 @@ "parser", "php" ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -760,60 +823,69 @@ "type": "tidelift" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "doctrine/orm", - "version": "v2.7.3", + "version": "2.19.6", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "d95e03ba660d50d785a9925f41927fef0ee553cf" + "reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/d95e03ba660d50d785a9925f41927fef0ee553cf", - "reference": "d95e03ba660d50d785a9925f41927fef0ee553cf", + "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 || ^3.0", - "doctrine/dbal": "^2.9.3", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^1.3.3 || ^2.0", - "ext-pdo": "*", - "ocramius/package-versions": "^1.2", - "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", - "phpstan/phpstan": "^0.12.18", - "phpunit/phpunit": "^7.5", - "symfony/yaml": "^3.4|^4.0|^5.0", - "vimeo/psalm": "^3.11" + "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/", @@ -848,63 +920,48 @@ "database", "orm" ], - "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/orm", - "type": "tidelift" - } - ], - "time": "2020-05-26T16:03:49+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": "2.0.0", + "version": "3.3.3", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "1dee036f22cd5dc0bc12132f1d1c38415907be55" + "reference": "b337726451f5d530df338fc7f68dee8781b49779" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/1dee036f22cd5dc0bc12132f1d1c38415907be55", - "reference": "1dee036f22cd5dc0bc12132f1d1c38415907be55", + "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.2", - "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", - "vimeo/psalm": "^3.11" + "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": "2.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/", @@ -938,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", @@ -946,6 +1003,10 @@ "orm", "persistence" ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/3.3.3" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -960,115 +1021,36 @@ "type": "tidelift" } ], - "time": "2020-05-12T19:32:44+00:00" - }, - { - "name": "doctrine/reflection", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/reflection.git", - "reference": "55e71912dfcd824b2fdd16f2d9afe15684cfce79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/reflection/zipball/55e71912dfcd824b2fdd16f2d9afe15684cfce79", - "reference": "55e71912dfcd824b2fdd16f2d9afe15684cfce79", - "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.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine 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-03-27T11:06:43+00:00" + "time": "2024-06-20T10:14:30+00:00" }, { "name": "elasticsearch/elasticsearch", - "version": "v7.7.0", + "version": "v7.17.2", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "1d90a7ff4fb1936dc4376f09d723af75714f6f05" + "reference": "2d302233f2bb0926812d82823bb820d405e130fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/1d90a7ff4fb1936dc4376f09d723af75714f6f05", - "reference": "1d90a7ff4fb1936dc4376f09d723af75714f6f05", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/2d302233f2bb0926812d82823bb820d405e130fc", + "reference": "2d302233f2bb0926812d82823bb820d405e130fc", "shasum": "" }, "require": { "ext-json": ">=1.3.7", "ezimuel/ringphp": "^1.1.2", - "php": "^7.1", - "psr/log": "~1.0" + "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": "^0.12", - "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": "*", @@ -1085,7 +1067,8 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "Apache-2.0", + "LGPL-2.1-only" ], "authors": [ { @@ -1101,27 +1084,31 @@ "elasticsearch", "search" ], - "time": "2020-05-13T15:19:26+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": "ezimuel/guzzlestreams", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/ezimuel/guzzlestreams.git", - "reference": "abe3791d231167f14eb80d413420d1eab91163a8" + "reference": "b4b5a025dfee70d6cd34c780e07330eb93d5b997" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/abe3791d231167f14eb80d413420d1eab91163a8", - "reference": "abe3791d231167f14eb80d413420d1eab91163a8", + "url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/b4b5a025dfee70d6cd34c780e07330eb93d5b997", + "reference": "b4b5a025dfee70d6cd34c780e07330eb93d5b997", "shasum": "" }, "require": { "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~9.0" }, "type": "library", "extra": { @@ -1151,20 +1138,23 @@ "Guzzle", "stream" ], - "time": "2020-02-14T23:11:50+00:00" + "support": { + "source": "https://github.com/ezimuel/guzzlestreams/tree/3.1.0" + }, + "time": "2022-10-24T12:58:50+00:00" }, { "name": "ezimuel/ringphp", - "version": "1.1.2", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/ezimuel/ringphp.git", - "reference": "0b78f89d8e0bb9e380046c31adfa40347e9f663b" + "reference": "7887fc8488013065f72f977dcb281994f5fde9f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezimuel/ringphp/zipball/0b78f89d8e0bb9e380046c31adfa40347e9f663b", - "reference": "0b78f89d8e0bb9e380046c31adfa40347e9f663b", + "url": "https://api.github.com/repos/ezimuel/ringphp/zipball/7887fc8488013065f72f977dcb281994f5fde9f4", + "reference": "7887fc8488013065f72f977dcb281994f5fde9f4", "shasum": "" }, "require": { @@ -1172,9 +1162,12 @@ "php": ">=5.4.0", "react/promise": "~2.0" }, + "replace": { + "guzzlehttp/ringphp": "self.version" + }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~9.0" }, "suggest": { "ext-curl": "Guzzle will use specific adapters if cURL is present" @@ -1202,20 +1195,23 @@ } ], "description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php", - "time": "2020-02-14T23:51:21+00:00" + "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": { @@ -1237,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", @@ -1257,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/" @@ -1316,180 +1300,148 @@ "homepage": "https://laminas.dev", "keywords": [ "code", - "laminas" + "laminas", + "laminasframework" + ], + "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" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } ], - "time": "2019-12-31T16:28:24+00:00" + "time": "2024-06-17T08:50:25+00:00" }, { - "name": "laminas/laminas-eventmanager", - "version": "3.2.1", + "name": "laminas/laminas-zendframework-bridge", + "version": "1.8.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-eventmanager.git", - "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748" + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "eb0d96c708b92177a92bc2239543d3ed523452c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", - "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/eb0d96c708b92177a92bc2239543d3ed523452c6", + "reference": "eb0d96c708b92177a92bc2239543d3ed523452c6", "shasum": "" }, "require": { - "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^5.6 || ^7.0" - }, - "replace": { - "zendframework/zend-eventmanager": "self.version" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" }, "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" + "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": "3.2-dev", - "dev-develop": "3.3-dev" + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" } }, "autoload": { + "files": [ + "src/autoload.php" + ], "psr-4": { - "Laminas\\EventManager\\": "src/" + "Laminas\\ZendFrameworkBridge\\": "src//" } }, "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" - }, - { - "name": "laminas/laminas-zendframework-bridge", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-zendframework-bridge.git", - "reference": "fcd87520e4943d968557803919523772475e8ea3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/fcd87520e4943d968557803919523772475e8ea3", - "reference": "fcd87520e4943d968557803919523772475e8ea3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1", - "squizlabs/php_codesniffer": "^3.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev", - "dev-develop": "1.1.x-dev" - }, - "laminas": { - "module": "Laminas\\ZendFrameworkBridge" - } - }, - "autoload": { - "files": [ - "src/autoload.php" - ], - "psr-4": { - "Laminas\\ZendFrameworkBridge\\": "src//" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "description": "Alias legacy ZF class names to Laminas Project equivalents.", "keywords": [ "ZendFramework", "autoloading", "laminas", "zf" ], + "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" } ], - "time": "2020-05-20T16:45:56+00:00" + "abandoned": true, + "time": "2023-11-24T13:56:19+00:00" }, { "name": "monolog/monolog", - "version": "2.1.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/38914429aac460e8e4616c8cb486ecb40ec90bb1", - "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1", + "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", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "php-parallel-lint/php-parallel-lint": "^1.0", - "phpspec/prophecy": "^1.6.1", - "phpunit/phpunit": "^8.5", - "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": { @@ -1505,16 +1457,20 @@ { "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" ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + }, "funding": [ { "url": "https://github.com/Seldaek", @@ -1525,104 +1481,40 @@ "type": "tidelift" } ], - "time": "2020-05-22T08:12:19+00:00" - }, - { - "name": "ocramius/package-versions", - "version": "1.8.0", - "source": { - "type": "git", - "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "421679846270a5772534828013a93be709fb13df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/421679846270a5772534828013a93be709fb13df", - "reference": "421679846270a5772534828013a93be709fb13df", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7.4.0" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "doctrine/coding-standard": "^7.0.2", - "ext-zip": "^1.15.0", - "infection/infection": "^0.15.3", - "phpunit/phpunit": "^9.0.1", - "vimeo/psalm": "^3.9.3" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.99.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/package-versions", - "type": "tidelift" - } - ], - "time": "2020-04-06T17:43:35+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "ocramius/proxy-manager", - "version": "2.8.0", + "version": "2.14.1", "source": { "type": "git", "url": "https://github.com/Ocramius/ProxyManager.git", - "reference": "ac1dd414fd114cfc0da9930e0ab46063c2f5e62a" + "reference": "3990d60ef79001badbab4927a6a811682274a0d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/ac1dd414fd114cfc0da9930e0ab46063c2f5e62a", - "reference": "ac1dd414fd114cfc0da9930e0ab46063c2f5e62a", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/3990d60ef79001badbab4927a6a811682274a0d1", + "reference": "3990d60ef79001badbab4927a6a811682274a0d1", "shasum": "" }, "require": { - "laminas/laminas-code": "^3.4.1", - "ocramius/package-versions": "^1.8.0", - "php": "~7.4.1", - "webimpress/safe-writer": "^2.0.1" + "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.16.2", - "nikic/php-parser": "^4.4.0", - "phpbench/phpbench": "^0.17.0", - "phpunit/phpunit": "^9.1.1", - "slevomat/coding-standard": "^5.0.4", - "squizlabs/php_codesniffer": "^3.5.4", - "vimeo/psalm": "^3.11.1" + "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)", @@ -1631,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" @@ -1649,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", @@ -1661,6 +1548,10 @@ "proxy pattern", "service proxies" ], + "support": { + "issues": "https://github.com/Ocramius/ProxyManager/issues", + "source": "https://github.com/Ocramius/ProxyManager/tree/2.14.1" + }, "funding": [ { "url": "https://github.com/Ocramius", @@ -1671,69 +1562,92 @@ "type": "tidelift" } ], - "time": "2020-04-13T14:42:16+00:00" + "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": { @@ -1753,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", @@ -1762,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": { @@ -1799,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)", @@ -1811,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", @@ -1857,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.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "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/", @@ -1894,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", @@ -1904,123 +1877,220 @@ "psr", "psr-3" ], - "time": "2020-03-23T09:12:05+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "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": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" }, { "name": "ramsey/uuid", - "version": "3.9.3", + "version": "4.7.6", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", - "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", "shasum": "" }, "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", "ext-json": "*", - "paragonie/random_compat": "^1 | ^2 | 9.99.99", - "php": "^5.4 | ^7 | ^8", - "symfony/polyfill-ctype": "^1.8" + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" }, "replace": { "rhumsaa/uuid": "self.version" }, "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", + "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-phpunit": "^0.3 | ^1.1", - "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", - "squizlabs/php_codesniffer": "^3.5" + "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-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).", + "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-console": "A console application for generating UUIDs with ramsey/uuid", "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "captainhook": { + "force-install": true } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - }, - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - } - ], - "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", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ "guid", "identifier", "uuid" ], - "time": "2020-02-21T04:36:14+00:00" + "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.8.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4" + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f3cff96a19736714524ca0dd1d4130de73dbbbc4", - "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4", + "url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831", "shasum": "" }, "require": { "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.5 || ^5.7 || ^4.8.36" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2029,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", @@ -2037,132 +2123,72 @@ "promise", "promises" ], - "time": "2020-05-12T15:16:56+00:00" - }, - { - "name": "symfony/amqp-messenger", - "version": "v5.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/amqp-messenger.git", - "reference": "f8bc975215edd516e25c9ea2b82a3026952bd8bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/amqp-messenger/zipball/f8bc975215edd516e25c9ea2b82a3026952bd8bc", - "reference": "f8bc975215edd516e25c9ea2b82a3026952bd8bc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/messenger": "^5.1" - }, - "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" - }, - "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.11.0" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony AMQP extension Messenger Bridge", - "homepage": "https://symfony.com", "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" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2023-11-16T16:16:50+00:00" }, { "name": "symfony/cache", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "653b1d1cefffedb67ed1932db59c0bb344adf088" + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/653b1d1cefffedb67ed1932db59c0bb344adf088", - "reference": "653b1d1cefffedb67ed1932db59c0bb344adf088", + "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/polyfill-php80": "^1.15", - "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|^3.0", - "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] @@ -2181,12 +2207,15 @@ "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" ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2201,33 +2230,34 @@ "type": "tidelift" } ], - "time": "2020-05-28T09:10:22+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.1.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "87c92f62c494626598e9148208aaa6d1716b8e3c" + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/87c92f62c494626598e9148208aaa6d1716b8e3c", - "reference": "87c92f62c494626598e9148208aaa6d1716b8e3c", + "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.1-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2259,6 +2289,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2273,51 +2306,37 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { - "name": "symfony/config", - "version": "v5.1.0", + "name": "symfony/clock", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "b8623ef3d99fe62a34baf7a111b576216965f880" + "url": "https://github.com/symfony/clock.git", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/b8623ef3d99fe62a34baf7a111b576216965f880", - "reference": "b8623ef3d99fe62a34baf7a111b576216965f880", + "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/filesystem": "^4.4|^5.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.15" - }, - "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.1-dev" - } - }, "autoload": { + "files": [ + "Resources/now.php" + ], "psr-4": { - "Symfony\\Component\\Config\\": "" + "Symfony\\Component\\Clock\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2329,16 +2348,24 @@ ], "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", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2353,64 +2380,43 @@ "type": "tidelift" } ], - "time": "2020-05-23T13:08:13+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/console", - "version": "v5.1.0", + "name": "symfony/config", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "00bed125812716d09b163f0727ef33bb49bf3448" + "url": "https://github.com/symfony/config.git", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/00bed125812716d09b163f0727ef33bb49bf3448", - "reference": "00bed125812716d09b163f0727ef33bb49bf3448", + "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/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "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/dotenv": "<5.1", - "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.1-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\Config\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2430,8 +2436,11 @@ "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", + "support": { + "source": "https://github.com/symfony/config/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2446,60 +2455,55 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v5.1.0", + "name": "symfony/console", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "6a6791e9584273b32eeb01790da4c7446d87a621" + "url": "https://github.com/symfony/console.git", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6a6791e9584273b32eeb01790da4c7446d87a621", - "reference": "6a6791e9584273b32eeb01790da4c7446d87a621", + "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15", - "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.1", - "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.1", - "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.1-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" + "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2519,8 +2523,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2535,34 +2548,51 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v2.1.2", + "name": "symfony/dependency-injection", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "8126f0be4ff984e4db0140e60917900a53facb49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", - "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8126f0be4ff984e4db0140e60917900a53facb49", + "reference": "8126f0be4ff984e4db0140e60917900a53facb49", "shasum": "" }, "require": { - "php": ">=7.1" + "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" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, + "type": "library", "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2571,16 +2601,19 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2595,49 +2628,38 @@ "type": "tidelift" } ], - "time": "2020-05-27T08:34:37+00:00" + "time": "2024-07-26T07:35:39+00:00" }, { - "name": "symfony/doctrine-messenger", - "version": "v5.1.0", + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", "source": { "type": "git", - "url": "https://github.com/symfony/doctrine-messenger.git", - "reference": "f0f0bae7249223973d768a4afdc9bd6388e7a055" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/f0f0bae7249223973d768a4afdc9bd6388e7a055", - "reference": "f0f0bae7249223973d768a4afdc9bd6388e7a055", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/messenger": "^5.1", - "symfony/service-contracts": "^1.1|^2" - }, - "conflict": { - "doctrine/persistence": "<1.3" + "php": ">=8.1" }, - "require-dev": { - "doctrine/dbal": "^2.6|^3.0", - "doctrine/orm": "^2.6.3", - "doctrine/persistence": "^1.3", - "symfony/property-access": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" - }, - "type": "symfony-bridge", + "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2646,16 +2668,19 @@ ], "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 Doctrine Messenger Bridge", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2670,35 +2695,34 @@ "type": "tidelift" } ], - "time": "2020-05-29T03:19:22+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/dotenv", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "42d2a18597f4c7cafc0e25b1ad6a1cbb4f2caf05" + "reference": "a26be30fd61678dab694a18a85084cea7673bbf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/42d2a18597f4c7cafc0e25b1ad6a1cbb4f2caf05", - "reference": "42d2a18597f4c7cafc0e25b1ad6a1cbb4f2caf05", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/a26be30fd61678dab694a18a85084cea7673bbf3", + "reference": "a26be30fd61678dab694a18a85084cea7673bbf3", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1" + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" }, "require-dev": { - "symfony/process": "^4.4|^5.0" + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Dotenv\\": "" @@ -2728,6 +2752,9 @@ "env", "environment" ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2742,39 +2769,40 @@ "type": "tidelift" } ], - "time": "2020-05-28T08:20:44+00:00" + "time": "2024-07-09T19:36:07+00:00" }, { "name": "symfony/error-handler", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896" + "reference": "432bb369952795c61ca1def65e078c4a80dad13c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", - "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/var-dumper": "^4.4|^5.0" + "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.1", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" @@ -2797,8 +2825,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ErrorHandler Component", + "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", @@ -2813,54 +2844,45 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-07-26T13:02:51+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.1.0", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -2883,8 +2905,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2899,33 +2924,34 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.1.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", - "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", + "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.1-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2957,6 +2983,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2971,32 +3000,31 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v5.1.0", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6e4320f06d5f2cce0d96530162491f4465179157" + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", - "reference": "6e4320f06d5f2cce0d96530162491f4465179157", + "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.1-dev" - } + "require-dev": { + "symfony/process": "^6.4|^7.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -3019,8 +3047,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3035,31 +3066,29 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/finder", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187" + "reference": "717c6329886f32dc65e27461f80f2a465412fdca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4298870062bfc667cb78d2b379be4bf5dec5f187", - "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187", + "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.1-dev" - } + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -3082,8 +3111,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3098,115 +3130,112 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-07-24T07:08:44+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "db39e29cf3a99692390e6d18fdc57d42e9e5a3c9" + "reference": "a32ec544bd501eb4619eb977860ad3076ee55061" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/db39e29cf3a99692390e6d18fdc57d42e9e5a3c9", - "reference": "db39e29cf3a99692390e6d18fdc57d42e9e5a3c9", + "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.1", - "symfony/error-handler": "^4.4.1|^5.0.1", - "symfony/event-dispatcher": "^5.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/polyfill-php80": "^1.15", - "symfony/routing": "^5.1" + "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": "<5.1", - "symfony/browser-kit": "<4.4", - "symfony/console": "<4.4", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<5.1", - "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": "^5.1", - "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": "^5.1", - "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-bundle": "^5.1", - "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", - "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" @@ -3229,8 +3258,11 @@ "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", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3245,43 +3277,42 @@ "type": "tidelift" } ], - "time": "2020-05-25T12:33:44+00:00" + "time": "2024-07-26T13:24:34+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e0d853bddc2b2cfb0d67b0b4496c03fffe1d37fa" + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e0d853bddc2b2cfb0d67b0b4496c03fffe1d37fa", - "reference": "e0d853bddc2b2cfb0d67b0b4496c03fffe1d37fa", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php83": "^1.27" }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0" + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" @@ -3304,8 +3335,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpFoundation Component", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3320,81 +3354,79 @@ "type": "tidelift" } ], - "time": "2020-05-24T12:18:07+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "75ff5327a7d6ede3ccc2fac3ebca9ed776b3e85c" + "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/75ff5327a7d6ede3ccc2fac3ebca9ed776b3e85c", - "reference": "75ff5327a7d6ede3ccc2fac3ebca9ed776b3e85c", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db9702f3a04cc471ec8c70e881825db26ac5f186", + "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "~1.0", - "symfony/deprecation-contracts": "^2.1", - "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", - "symfony/polyfill-php80": "^1.15" + "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/console": "<4.4", - "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpKernel\\": "" @@ -3417,8 +3449,11 @@ "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", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3433,58 +3468,51 @@ "type": "tidelift" } ], - "time": "2020-05-31T06:14:18+00:00" + "time": "2024-07-26T14:58:15+00:00" }, { "name": "symfony/messenger", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "4e668843aee9f035baaefbac6961a52d2b293928" + "reference": "604e182a7758ceea35921a8ad5dd492a6e13bae4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/4e668843aee9f035baaefbac6961a52d2b293928", - "reference": "4e668843aee9f035baaefbac6961a52d2b293928", + "url": "https://api.github.com/repos/symfony/messenger/zipball/604e182a7758ceea35921a8ad5dd492a6e13bae4", + "reference": "604e182a7758ceea35921a8ad5dd492a6e13bae4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "~1.0", - "symfony/amqp-messenger": "^5.1", - "symfony/deprecation-contracts": "^2.1", - "symfony/doctrine-messenger": "^5.1", - "symfony/polyfill-php80": "^1.15", - "symfony/redis-messenger": "^5.1" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/clock": "^6.4|^7.0" }, "conflict": { - "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": { - "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\": "" @@ -3507,8 +3535,11 @@ "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", + "support": { + "source": "https://github.com/symfony/messenger/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3523,41 +3554,45 @@ "type": "tidelift" } ], - "time": "2020-05-24T08:49:09+00:00" + "time": "2024-07-09T19:36:07+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.17.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", - "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3581,6 +3616,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3595,41 +3633,42 @@ "type": "tidelift" } ], - "time": "2020-05-12T16:14:59+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.17.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e094b0770f7833fdf257e6ba4775be4e258230b2" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e094b0770f7833fdf257e6ba4775be4e258230b2", - "reference": "e094b0770f7833fdf257e6ba4775be4e258230b2", + "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-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3655,6 +3694,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3669,41 +3711,42 @@ "type": "tidelift" } ], - "time": "2020-05-12T16:47:27+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.17.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/1357b1d168eb7f68ad6a134838e46b0b159444a9", - "reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3732,6 +3775,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3746,41 +3792,45 @@ "type": "tidelift" } ], - "time": "2020-05-12T16:14:59+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.17.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", - "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", + "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.17-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": [ @@ -3805,6 +3855,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3819,41 +3872,39 @@ "type": "tidelift" } ], - "time": "2020-05-12T16:47:27+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.17.0", + "name": "symfony/polyfill-php72", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "10112722600777e02d2745716b70c5db4ca70442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", - "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", + "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.17-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], - "classmap": [ - "Resources/stubs" - ] + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3869,7 +3920,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 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3877,6 +3928,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3891,38 +3945,39 @@ "type": "tidelift" } ], - "time": "2020-05-12T16:47:27+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.17.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/5e30b2799bc1ad68f7feb62b60a73743589438dd", - "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { - "php": ">=7.0.8" + "php": ">=7.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3953,6 +4008,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3967,42 +4025,41 @@ "type": "tidelift" } ], - "time": "2020-05-12T16:47:27+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { - "name": "symfony/redis-messenger", - "version": "v5.1.0", + "name": "symfony/polyfill-php83", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/redis-messenger.git", - "reference": "d37c6a267d79b0e0dff5e2b96c5f07886353d7a2" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/redis-messenger/zipball/d37c6a267d79b0e0dff5e2b96c5f07886353d7a2", - "reference": "d37c6a267d79b0e0dff5e2b96c5f07886353d7a2", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/messenger": "^5.1" - }, - "require-dev": { - "symfony/property-access": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "php": ">=7.1" }, - "type": "symfony-bridge", + "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.1-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Messenger\\Bridge\\Redis\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4011,16 +4068,25 @@ ], "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 Redis extension Messenger Bridge", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4035,54 +4101,40 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-06-19T12:35:24+00:00" }, { "name": "symfony/routing", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "95cf30145b26c758d6d832aa2d0de3128978d556" + "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/95cf30145b26c758d6d832aa2d0de3128978d556", - "reference": "95cf30145b26c758d6d832aa2d0de3128978d556", + "url": "https://api.github.com/repos/symfony/routing/zipball/8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", + "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "symfony/config": "<5.0", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.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" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Routing\\": "" @@ -4105,7 +4157,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Routing Component", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ "router", @@ -4113,6 +4165,9 @@ "uri", "url" ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4127,39 +4182,47 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.1.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", - "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, - "suggest": { - "symfony/service-implementation": "" + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4185,6 +4248,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4199,49 +4265,48 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/string", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "90c2a5103f07feb19069379f3abdcdbacc7753a9" + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/90c2a5103f07feb19069379f3abdcdbacc7753a9", - "reference": "90c2a5103f07feb19069379f3abdcdbacc7753a9", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { - "php": ">=7.2.5", + "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", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -4260,7 +4325,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony String component", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", @@ -4270,6 +4335,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4284,38 +4352,42 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-07-22T10:25:37+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.1.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e" + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e5ca07c8f817f865f618aa072c2fe8e0e637340e", - "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "shasum": "" }, "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "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": [ @@ -4341,6 +4413,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4355,84 +4430,74 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/twig-bridge", - "version": "v5.1.0", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "04f57638e591b23d06f72dca2f123605dbbe3a75" + "reference": "96e6e12a63db80bcedefc012042d2cb2d1a015f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/04f57638e591b23d06f72dca2f123605dbbe3a75", - "reference": "04f57638e591b23d06f72dca2f123605dbbe3a75", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/96e6e12a63db80bcedefc012042d2cb2d1a015f8", + "reference": "96e6e12a63db80bcedefc012042d2cb2d1a015f8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.10|^3.0" + "php": ">=8.2", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.9" }, "conflict": { - "symfony/console": "<4.4", - "symfony/form": "<5.1", - "symfony/http-foundation": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.0", - "symfony/workflow": "<4.4" + "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", - "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.1", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", + "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/routing": "^4.4|^5.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.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" + "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", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bridge\\Twig\\": "" @@ -4455,8 +4520,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Twig Bridge", + "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", @@ -4471,57 +4539,49 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/twig-bundle", - "version": "v5.1.0", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "8898ef8aea8fa48638e15ce00c7c6318ce570ce1" + "reference": "d48c2f08c2f315e749f0e18fc4945b7be8afe1e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/8898ef8aea8fa48638e15ce00c7c6318ce570ce1", - "reference": "8898ef8aea8fa48638e15ce00c7c6318ce570ce1", + "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" @@ -4544,8 +4604,11 @@ "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", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4560,83 +4623,68 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/validator", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "7a012b935dffe51d37ce001f62fbc6cb307d96df" + "reference": "ba711a6cfc008544dad059abb3c1d997f1472237" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/7a012b935dffe51d37ce001f62fbc6cb307d96df", - "reference": "7a012b935dffe51d37ce001f62fbc6cb307d96df", + "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/polyfill-php80": "^1.15", - "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/expression-language": "<5.1", - "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": "^5.1", - "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/mime": "^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 and the ExpressionLanguageSyntax constraints", - "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Validator\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4653,8 +4701,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Validator Component", + "description": "Provides tools to validate values", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4669,51 +4720,41 @@ "type": "tidelift" } ], - "time": "2020-05-30T21:58:16+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.1.0", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "46a942903059b0b05e601f00eb64179e05578c0f" + "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46a942903059b0b05e601f00eb64179e05578c0f", - "reference": "46a942903059b0b05e601f00eb64179e05578c0f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/86af4617cca75a6e28598f49ae0690f3b9d4591f", + "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "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.1-dev" - } - }, "autoload": { "files": [ "Resources/functions/dump.php" @@ -4739,12 +4780,15 @@ "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" ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4759,35 +4803,31 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.1.0", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "76cf21551652b14d80abf13243159fbdf4bbef22" + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/76cf21551652b14d80abf13243159fbdf4bbef22", - "reference": "76cf21551652b14d80abf13243159fbdf4bbef22", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.2" }, "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9" + "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\VarExporter\\": "" @@ -4810,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", @@ -4818,8 +4858,13 @@ "export", "hydrate", "instantiate", + "lazy-loading", + "proxy", "serialize" ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4834,45 +4879,36 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/yaml", - "version": "v5.1.0", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ea342353a3ef4f453809acc4ebc55382231d4d23" + "reference": "fa34c77015aa6720469db7003567b9f772492bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ea342353a3ef4f453809acc4ebc55382231d4d23", - "reference": "ea342353a3ef4f453809acc4ebc55382231d4d23", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "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.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" @@ -4895,8 +4931,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4911,38 +4950,41 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "twig/twig", - "version": "v3.0.3", + "version": "v3.10.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3b88ccd180a6b61ebb517aea3b1a8906762a1dc2" + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3b88ccd180a6b61ebb517aea3b1a8906762a1dc2", - "reference": "3b88ccd180a6b61ebb517aea3b1a8906762a1dc2", + "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/" } @@ -4973,34 +5015,49 @@ "keywords": [ "templating" ], - "time": "2020-02-11T15:33:47+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.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/webimpress/safe-writer.git", - "reference": "d6e879960febb307c112538997316371f1e95b12" + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/d6e879960febb307c112538997316371f1e95b12", - "reference": "d6e879960febb307c112538997316371f1e95b12", + "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.5.2 || ^9.0.1", - "webimpress/coding-standard": "^1.1.4" + "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" } }, @@ -5021,65 +5078,59 @@ "safe writer", "webimpress" ], + "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": "2020-03-21T15:49:08+00:00" + "time": "2021-04-19T16:34:45+00:00" } ], "packages-dev": [ { - "name": "behat/behat", - "version": "v3.7.0", + "name": "amphp/amp", + "version": "v2.6.4", "source": { "type": "git", - "url": "https://github.com/Behat/Behat.git", - "reference": "08052f739619a9e9f62f457a67302f0715e6dd13" + "url": "https://github.com/amphp/amp.git", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Behat/zipball/08052f739619a9e9f62f457a67302f0715e6dd13", - "reference": "08052f739619a9e9f62f457a67302f0715e6dd13", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { - "behat/gherkin": "^4.6.0", - "behat/transliterator": "^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": { - "container-interop/container-interop": "^1.2", - "herrera-io/box": "~1.6.1", - "phpunit/phpunit": "^4.8.36 || ^6.5.14 || ^7.5.20", - "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.6.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/" + "Amp\\": "lib" } }, "notification-url": "https://packagist.org/downloads/", @@ -5088,12 +5139,185 @@ ], "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": "Scenario-oriented BDD framework for PHP 5.3", + "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", "homepage": "http://behat.org/", "keywords": [ "Agile", @@ -5109,29 +5333,33 @@ "symfony", "testing" ], - "time": "2020-06-03T13:08:44+00:00" + "support": { + "issues": "https://github.com/Behat/Behat/issues", + "source": "https://github.com/Behat/Behat/tree/v3.14.0" + }, + "time": "2023-12-09T13:55:02+00:00" }, { "name": "behat/gherkin", - "version": "v4.6.2", + "version": "v4.9.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31" + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/51ac4500c4dc30cbaaabcd2f25694299df666a31", - "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31", + "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" @@ -5139,7 +5367,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "4.x-dev" } }, "autoload": { @@ -5158,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", @@ -5168,34 +5396,39 @@ "gherkin", "parser" ], - "time": "2020-03-17T14:03:26+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.8.1", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/minkphp/Mink.git", - "reference": "07c6a9fe3fa98c2de074b25d9ed26c22904e3887" + "reference": "d8527fdf8785aad38455fb426af457ab9937aece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/Mink/zipball/07c6a9fe3fa98c2de074b25d9ed26c22904e3887", - "reference": "07c6a9fe3fa98c2de074b25d9ed26c22904e3887", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/d8527fdf8785aad38455fb426af457ab9937aece", + "reference": "d8527fdf8785aad38455fb426af457ab9937aece", "shasum": "" }, "require": { - "php": ">=5.3.1", - "symfony/css-selector": "^2.7|^3.0|^4.0|^5.0" + "php": ">=7.2", + "symfony/css-selector": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20", - "symfony/debug": "^2.7|^3.0|^4.0", - "symfony/phpunit-bridge": "^3.4.38 || ^5.0.5" + "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)", "dmore/chrome-mink-driver": "fast and JS-enabled driver for any app (requires chromium or google chrome)" @@ -5203,7 +5436,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -5223,43 +5456,54 @@ } ], "description": "Browser controller/emulator abstraction for PHP", - "homepage": "http://mink.behat.org/", + "homepage": "https://mink.behat.org/", "keywords": [ "browser", "testing", "web" ], - "time": "2020-03-11T15:45:53+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": "v1.3.4", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", - "reference": "e3b90840022ebcd544c7b394a3c9597ae242cbee" + "reference": "16d53476e42827ed3aafbfa4fde17a1743eafd50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/e3b90840022ebcd544c7b394a3c9597ae242cbee", - "reference": "e3b90840022ebcd544c7b394a3c9597ae242cbee", + "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", - "symfony/dom-crawler": "~2.3|~3.0|~4.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/debug": "^2.7|^3.0|^4.0", - "symfony/http-kernel": "~2.3|~3.0|~4.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": { @@ -5279,41 +5523,45 @@ } ], "description": "Symfony2 BrowserKit driver for Mink framework", - "homepage": "http://mink.behat.org/", + "homepage": "https://mink.behat.org/", "keywords": [ "Mink", "Symfony2", "browser", "testing" ], - "time": "2020-03-11T09:49:45+00:00" + "support": { + "issues": "https://github.com/minkphp/MinkBrowserKitDriver/issues", + "source": "https://github.com/minkphp/MinkBrowserKitDriver/tree/v2.2.0" + }, + "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": { @@ -5325,40 +5573,655 @@ "license": [ "Artistic-1.0" ], - "description": "String transliterator", + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "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": "codelytv/coding-style", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/CodelyTV/php-coding_style-codely.git", + "reference": "41d7e6b651619467b05018666606a1ef0958263e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CodelyTV/php-coding_style-codely/zipball/41d7e6b651619467b05018666606a1ef0958263e", + "reference": "41d7e6b651619467b05018666606a1ef0958263e", + "shasum": "" + }, + "require": { + "symplify/easy-coding-standard": "^12.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "CodelyTv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "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" + } + ], + "time": "2024-08-05T14:17:14+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "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": "composer/pcre", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.8" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.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": "2024-07-25T09:36:02+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.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": [ - "i18n", - "slug", - "transliterator" + "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": "2020-01-14T16:39:13+00:00" + "time": "2024-02-07T09:43:46+00:00" }, { "name": "friends-of-behat/mink-extension", - "version": "v2.4.0", + "version": "v2.7.5", "source": { "type": "git", "url": "https://github.com/FriendsOfBehat/MinkExtension.git", - "reference": "ca8796a20f2b39ea275860d1cee7b863447dfe06" + "reference": "854336030e11983f580f49faad1b49a1238f9846" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfBehat/MinkExtension/zipball/ca8796a20f2b39ea275860d1cee7b863447dfe06", - "reference": "ca8796a20f2b39ea275860d1cee7b863447dfe06", + "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.2", - "symfony/config": "^4.4|^5.0" + "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", - "phpspec/phpspec": "^6.0" + "behat/mink-goutte-driver": "^1.1 || ^2.0", + "phpspec/phpspec": "^6.0 || ^7.0 || 7.1.x-dev" }, "type": "behat-extension", "extra": { @@ -5393,54 +6256,55 @@ "test", "web" ], - "time": "2020-01-15T17:16:57+00:00" + "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.1.0-BETA.1", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/FriendsOfBehat/SymfonyExtension.git", - "reference": "c2afff0d15e4ad5fb2cc085baef17a051b9c3ad9" + "reference": "dfb1c9c96cc0fb7c8e1caa060695426a12e1efbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfBehat/SymfonyExtension/zipball/c2afff0d15e4ad5fb2cc085baef17a051b9c3ad9", - "reference": "c2afff0d15e4ad5fb2cc085baef17a051b9c3ad9", + "url": "https://api.github.com/repos/FriendsOfBehat/SymfonyExtension/zipball/dfb1c9c96cc0fb7c8e1caa060695426a12e1efbd", + "reference": "dfb1c9c96cc0fb7c8e1caa060695426a12e1efbd", "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" - }, - "conflict": { - "symplify/package-builder": "^7.2", - "symplify/smart-file-system": "^7.2" + "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": "^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" + "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-browserkit-driver": "^1.3" + "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.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -5460,140 +6324,164 @@ } ], "description": "Integrates Behat with Symfony.", - "time": "2020-01-15T18:02:23+00:00" + "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": "fzaninotto/faker", - "version": "v1.9.1", + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", "source": { "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", - "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" }, "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^2.9.2" + "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": "1.9-dev" + "dev-master": "2.1-dev" } }, "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } + "classmap": [ + "hamcrest" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "FranΓ§ois Zaninotto" - } + "BSD-3-Clause" ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "This is the PHP port of Hamcrest Matchers", "keywords": [ - "data", - "faker", - "fixtures" + "test" ], - "time": "2019-12-12T13:22:17+00:00" + "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": "hamcrest/hamcrest-php", - "version": "v2.0.0", + "name": "masterminds/html5", + "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", "shasum": "" }, "require": { - "php": "^5.3|^7.0" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" + "ext-dom": "*", + "php": ">=5.3.0" }, "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "^1.0" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.7-dev" } }, "autoload": { - "classmap": [ - "hamcrest" - ] + "psr-4": { + "Masterminds\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD" + "MIT" ], - "description": "This is the PHP port of Hamcrest Matchers", + "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": [ - "test" + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" ], - "time": "2016-01-20T08:20:44+00:00" + "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.4.0", + "version": "1.6.12", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "6c6a7c533469873deacf998237e7649fc6b36223" + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/6c6a7c533469873deacf998237e7649fc6b36223", - "reference": "6c6a7c533469873deacf998237e7649fc6b36223", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "~2.0", + "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.3.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.0.0 || ^9.0.0" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, "autoload": { - "psr-0": { - "Mockery": "library/" + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -5604,12 +6492,20 @@ { "name": "PΓ‘draic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.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", @@ -5626,41 +6522,50 @@ "test double", "testing" ], - "time": "2020-05-19T14:25:16+00:00" + "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.9.5", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", - "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "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": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5674,32 +6579,214 @@ "object", "object graph" ], - "time": "2020-01-17T21:11:47+00:00" + "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/cweiske/jsonmapper.git", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "shasum": "" + }, + "require": { + "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": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + }, + "time": "2024-03-17T08:10:35+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "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": { + "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": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "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": { @@ -5729,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": { @@ -5776,29 +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": "phpat/phpat", + "version": "0.10.18", + "source": { + "type": "git", + "url": "https://github.com/carlosas/phpat.git", + "reference": "4c29e330fb306876bca3174aa4b097d0d8611964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/carlosas/phpat/zipball/4c29e330fb306876bca3174aa4b097d0d8611964", + "reference": "4c29e330fb306876bca3174aa4b097d0d8611964", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^1.3" + }, + "require-dev": { + "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.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", - "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -5825,32 +6983,43 @@ "reflection", "static analysis" ], - "time": "2020-04-27T09:25:28+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": "5.1.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { - "ext-filter": "^7.1", - "php": "^7.2", - "phpdocumentor/reflection-common": "^2.0", - "phpdocumentor/type-resolver": "^1.0", - "webmozart/assert": "^1" + "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", - "mockery/mockery": "^1" + "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": { @@ -5874,38 +7043,50 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "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": "2020-02-22T12:28:44+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.1.0", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", - "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { - "php": "^7.2", - "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.2", - "mockery/mockery": "~1" + "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": { @@ -5924,108 +7105,240 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-02-18T18:59:58+00:00" + "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": "phpspec/prophecy", - "version": "v1.10.3", + "name": "phpmd/phpmd", + "version": "2.15.0", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "451c3cd1418cf640de218914901e51b064abb093" + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", - "reference": "451c3cd1418cf640de218914901e51b064abb093", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", "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" + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "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", - "extra": { - "branch-alias": { - "dev-master": "1.10.x-dev" + "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": { - "Prophecy\\": "src/Prophecy" + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "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": "phpstan/phpstan", + "version": "1.11.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "e370bcddadaede0c1716338b262346f40d296f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82", + "reference": "e370bcddadaede0c1716338b262346f40d296f82", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ { - "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-03-05T15:02:03+00:00" + "time": "2024-08-01T16:25:18+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "8.0.2", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", - "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": "^7.3", - "phpunit/php-file-iterator": "^3.0", - "phpunit/php-text-template": "^2.0", - "phpunit/php-token-stream": "^4.0", - "sebastian/code-unit-reverse-lookup": "^2.0", - "sebastian/environment": "^5.0", - "sebastian/version": "^3.0", - "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": "^9.0" + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "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": "8.0-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -6051,33 +7364,38 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-05-23T08:02:54+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.1", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4", - "reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -6107,34 +7425,38 @@ "filesystem", "iterator" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-04-18T05:02:12+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.0.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a" + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7579d5a1ba7f3ac11c80004d205877911315ae7a", - "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-pcntl": "*" @@ -6142,7 +7464,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -6166,24 +7488,37 @@ "keywords": [ "process" ], - "time": "2020-02-07T06:06:11+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.0", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346" + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/526dc996cc0ebdfa428cd2dfccd79b7b53fee346", - "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -6212,35 +7547,137 @@ "keywords": [ "template" ], - "time": "2020-02-01T07:43:44+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" }, { "name": "phpunit/php-timer", - "version": "3.1.4", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "dc9368fae6ef2ffa57eba80a7410bcef81df6258" + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/dc9368fae6ef2ffa57eba80a7410bcef81df6258", - "reference": "dc9368fae6ef2ffa57eba80a7410bcef81df6258", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "49d7820565836236411f5dc002d16dd689cde42f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "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": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -6256,171 +7693,271 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "funding": [ + "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": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "name": "Matt Brown", + "email": "github@muglug.com" } ], - "time": "2020-04-20T06:00:37+00:00" + "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": "phpunit/php-token-stream", - "version": "4.0.1", + "name": "psalm/plugin-symfony", + "version": "v5.2.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c" + "url": "https://github.com/psalm/psalm-plugin-symfony.git", + "reference": "fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c", - "reference": "cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c", + "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196", + "reference": "fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.3" + "ext-simplexml": "*", + "php": "^8.1", + "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", + "vimeo/psalm": "^5.16" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "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" }, - "type": "library", + "suggest": { + "weirdan/doctrine-psalm-plugin": "If Doctrine is used, it is recommended install this plugin" + }, + "type": "psalm-plugin", "extra": { - "branch-alias": { - "dev-master": "4.0-dev" + "psalm": { + "pluginClass": "Psalm\\SymfonyPsalmPlugin\\Plugin" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psalm\\SymfonyPsalmPlugin\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "name": "Farhad Safarov", + "email": "farhad.safarov@gmail.com" } ], - "time": "2020-05-06T09:56:31+00:00" + "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": "phpunit/phpunit", - "version": "9.1.5", + "name": "rector/rector", + "version": "0.18.13", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1b570cd7edbe136055bf5f651857dc8af6b829d2" + "url": "https://github.com/rectorphp/rector.git", + "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1b570cd7edbe136055bf5f651857dc8af6b829d2", - "reference": "1b570cd7edbe136055bf5f651857dc8af6b829d2", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/f8011a76d36aa4f839f60f3b4f97707d97176618", + "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2.0", - "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.3", - "phpspec/prophecy": "^1.8.1", - "phpunit/php-code-coverage": "^8.0.1", - "phpunit/php-file-iterator": "^3.0", - "phpunit/php-invoker": "^3.0", - "phpunit/php-text-template": "^2.0", - "phpunit/php-timer": "^3.1.4", - "sebastian/code-unit": "^1.0.2", - "sebastian/comparator": "^4.0", - "sebastian/diff": "^4.0", - "sebastian/environment": "^5.0.1", - "sebastian/exporter": "^4.0", - "sebastian/global-state": "^4.0", - "sebastian/object-enumerator": "^4.0", - "sebastian/resource-operations": "^3.0", - "sebastian/type": "^2.0", - "sebastian/version": "^3.0" - }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0" + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.10.35" }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" }, "bin": [ - "phpunit" + "bin/rector" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.1-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ], "files": [ - "src/Framework/Assert/Functions.php" + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "MIT" ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Instant Upgrade and Automated Refactoring of any PHP code", "keywords": [ - "phpunit", - "testing", - "xunit" + "automation", + "dev", + "migration", + "refactoring" ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.18.13" + }, "funding": [ { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/tomasvotruba", "type": "github" } ], - "time": "2020-05-22T13:54:05+00:00" + "time": "2023-12-20T16:08:01+00:00" }, { "name": "roave/security-advisories", @@ -6428,249 +7965,776 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "55922f51129488c246a776ff944463605d447da0" + "reference": "ff7456939acba6dd515a8a10aad66be6bc1b8dc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/55922f51129488c246a776ff944463605d447da0", - "reference": "55922f51129488c246a776ff944463605d447da0", + "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", - "bagisto/bagisto": "<0.1.5", + "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", - "bolt/bolt": "<3.6.10", + "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", - "buddypress/buddypress": "<5.1.2", + "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", - "centreon/centreon": "<18.10.8|>=19,<19.4.5", + "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", - "codeigniter/framework": "<=3.0.6", - "composer/composer": "<=1-alpha.11", + "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", - "dolibarr/dolibarr": "<11.0.4", - "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.70|>=8,<8.7.14|>=8.8,<8.8.6", - "drupal/drupal": ">=7,<7.70|>=8,<8.7.14|>=8.8,<8.8.6", + "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", - "enshrined/svg-sanitize": "<0.13.1", + "enhavo/enhavo-app": "<=0.13.1", + "enshrined/svg-sanitize": "<0.15", "erusev/parsedown": "<1.7.2", - "ezsystems/demobundle": ">=5.4,<5.4.6.1", - "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", - "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", - "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6", - "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2", + "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": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.1|>=6,<6.7.9.1|>=6.8,<6.13.6.2|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.6.2", - "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.1|>=2011,<2017.12.7.2|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.4.2", - "ezsystems/repository-forms": ">=2.3,<2.3.2.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", - "getgrav/grav": "<1.7-beta.8", - "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": ">=7,<7.1.2", + "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|>=7,<7.1.2", - "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", - "librenms/librenms": "<1.53", - "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.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.9.3|>=2,<2.1.5", + "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", - "phpfastcache/phpfastcache": ">=5,<5.0.13", - "phpmailer/phpmailer": "<6.1.6", - "phpmyadmin/phpmyadmin": "<4.9.2", - "phpoffice/phpexcel": "<1.8.2", - "phpoffice/phpspreadsheet": "<1.8", + "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", - "pimcore/pimcore": "<6.3", + "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", - "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2", - "propel/propel": ">=2-alpha.1,<=2-alpha.7", + "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", - "robrichards/xmlseclibs": "<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", + "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", + "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/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1", - "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2", - "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", - "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", + "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": "<4.4.5|>=4.5,<4.5.2", - "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/subsites": ">=2,<2.1.1", + "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", + "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/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", - "ssddanbrown/bookstack": "<0.29.2", - "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.3.13|>=1.4,<1.4.6|>=1.5,<1.5.1|>=1.6,<1.6.3", - "sylius/sylius": "<1.3.16|>=1.4,<1.4.12|>=1.5,<1.5.9|>=1.6,<1.6.5", - "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", + "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/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,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "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|>=4.4,<4.4.7|>=5,<5.0.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|>=4.4,<4.4.7|>=5,<5.0.7", - "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|>=4.4,<4.4.7|>=5,<5.0.7", + "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", + "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", - "tecnickcom/tcpdf": "<6.2.22", + "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.17|>=10,<10.4.2", - "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2", - "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", - "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", + "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", + "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.14,<1.1.15", - "yiisoft/yii2": "<2.0.15", + "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", - "yourls/yourls": "<1.7.4", + "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/", @@ -6690,6 +8754,13 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, "funding": [ { "url": "https://github.com/Ocramius", @@ -6700,27 +8771,83 @@ "type": "tidelift" } ], - "time": "2020-05-28T00:01:39+00:00" + "time": "2024-08-05T15:04:41+00:00" }, { - "name": "sebastian/code-unit", + "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": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5" + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ac958085bc19fcd1d36425c781ef4cbb5b06e2a5", - "reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -6746,33 +8873,37 @@ ], "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-04-30T05:58:10+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5b5dbe0044085ac41df47e79d34911a15b96d82e", - "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -6797,29 +8928,39 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2020-02-07T06:20:13+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": "4.0.0", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85b3435da967696ed618ff745f32be3ff4a2b8e8", - "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "php": "^7.3", + "php": ">=7.3", "sebastian/diff": "^4.0", "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -6861,27 +9002,94 @@ "compare", "equality" ], - "time": "2020-02-07T06:08:51+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": "4.0.1", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3e523c576f29dacecff309f35e4cc5a5c168e78a", - "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0", + "phpunit/phpunit": "^9.3", "symfony/process": "^4.2 || ^5" }, "type": "library", @@ -6917,33 +9125,37 @@ "unidiff", "unified diff" ], + "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": "2020-05-08T05:01:12+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "5.1.0", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c753f04d68cd489b6973cf9b4e505e191af3b05c", - "reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -6951,7 +9163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -6976,35 +9188,39 @@ "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-04-14T13:36:52+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.0", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "80c26562e964016538f832f305b2286e1ec29566" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/80c26562e964016538f832f305b2286e1ec29566", - "reference": "80c26562e964016538f832f305b2286e1ec29566", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { - "php": "^7.3", + "php": ">=7.3", "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -7044,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": "2020-02-07T06:10:52+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": "4.0.0", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bdb1e7c79e592b8c82cb1699be3c8743119b8a72", - "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { - "php": "^7.3", + "php": ">=7.3", "sebastian/object-reflector": "^2.0", "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -7080,7 +9306,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -7098,34 +9324,101 @@ "email": "sebastian@phpunit.de" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.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": "2020-02-07T06:11:37+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.0", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67516b175550abad905dc952f43285957ef4363" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67516b175550abad905dc952f43285957ef4363", - "reference": "e67516b175550abad905dc952f43285957ef4363", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": "^7.3", + "php": ">=7.3", "sebastian/object-reflector": "^2.0", "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -7150,27 +9443,37 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2020-02-07T06:12:23+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": "2.0.0", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", - "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -7195,27 +9498,37 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2020-02-07T06:19:40+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": "4.0.0", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cdd86616411fc3062368b720b0425de10bd3d579" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cdd86616411fc3062368b720b0425de10bd3d579", - "reference": "cdd86616411fc3062368b720b0425de10bd3d579", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -7247,25 +9560,35 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2020-02-07T06:18:20+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": "3.0.0", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", - "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { "phpunit/phpunit": "^9.0" @@ -7273,7 +9596,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -7293,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": "2020-02-07T06:13:02+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": "2.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", - "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.2" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -7339,30 +9671,34 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-06-01T12:21:09+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "0411bde656dce64202b39c2f4473993a9081d39e" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/0411bde656dce64202b39c2f4473993a9081d39e", - "reference": "0411bde656dce64202b39c2f4473993a9081d39e", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": "^7.3" + "php": ">=7.3" }, "type": "library", "extra": { @@ -7388,48 +9724,51 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2020-01-21T06:36:37+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": "v4.4.9", + "name": "spatie/array-to-xml", + "version": "3.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "f53310646af9901292488b2ff36e26ea10f545f5" + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f53310646af9901292488b2ff36e26ea10f545f5", - "reference": "f53310646af9901292488b2ff36e26ea10f545f5", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/dom-crawler": "^3.4|^4.0|^5.0" + "ext-dom": "*", + "php": "^8.0" }, "require-dev": { - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/http-client": "^4.3|^5.0", - "symfony/mime": "^4.3|^5.0", - "symfony/process": "^3.4|^4.0|^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": "4.4-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": [ @@ -7437,58 +9776,62 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" } ], - "description": "Symfony BrowserKit Component", - "homepage": "https://symfony.com", + "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://symfony.com/sponsor", + "url": "https://spatie.be/open-source/support-us", "type": "custom" }, { - "url": "https://github.com/fabpot", + "url": "https://github.com/spatie", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2020-05-22T17:28:00+00:00" + "time": "2024-05-01T10:20:27+00:00" }, { - "name": "symfony/css-selector", - "version": "v5.1.0", + "name": "symfony/browser-kit", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9" + "url": "https://github.com/symfony/browser-kit.git", + "reference": "9c13742e3175b5815e272b981876ae329bec2040" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/e544e24472d4c97b2d11ade7caacd446727c6bf9", - "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/9c13742e3175b5815e272b981876ae329bec2040", + "reference": "9c13742e3175b5815e272b981876ae329bec2040", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.2", + "symfony/dom-crawler": "^6.4|^7.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-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/" @@ -7503,17 +9846,16 @@ "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", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -7528,42 +9870,29 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/debug", - "version": "v4.4.9", + "name": "symfony/css-selector", + "version": "v7.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6" + "url": "https://github.com/symfony/css-selector.git", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/28f92d08bb6d1fddf8158e02c194ad43870007e6", - "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", "shasum": "" }, "require": { - "php": ">=7.1.3", - "psr/log": "~1.0", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" + "php": ">=8.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\Debug\\": "" + "Symfony\\Component\\CssSelector\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7578,13 +9907,20 @@ "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", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -7599,43 +9935,32 @@ "type": "tidelift" } ], - "time": "2020-05-24T08:33:35+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.9", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "c18354d5a0bb84c945f6257c51b971d52f10c614" + "reference": "01ce8174447f1f1dd33a5854b01beef79061d9fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c18354d5a0bb84c945f6257c51b971d52f10c614", - "reference": "c18354d5a0bb84c945f6257c51b971d52f10c614", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/01ce8174447f1f1dd33a5854b01beef79061d9fa", + "reference": "01ce8174447f1f1dd33a5854b01beef79061d9fa", "shasum": "" }, "require": { - "php": ">=7.1.3", + "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": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/css-selector": "" + "symfony/css-selector": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\DomCrawler\\": "" @@ -7658,8 +9983,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DomCrawler Component", + "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.1.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -7674,42 +10002,62 @@ "type": "tidelift" } ], - "time": "2020-05-23T00:03:06+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { - "name": "symfony/proxy-manager-bridge", - "version": "v5.1.0", + "name": "symfony/translation", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "65f834babc07abe54c8e0f5bb5cb01f953480953" + "url": "https://github.com/symfony/translation.git", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/65f834babc07abe54c8e0f5bb5cb01f953480953", - "reference": "65f834babc07abe54c8e0f5bb5cb01f953480953", + "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.1-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/" @@ -7729,8 +10077,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ProxyManager Bridge", + "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.1.3" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -7745,119 +10096,88 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { - "name": "symfony/translation", - "version": "v5.1.0", + "name": "symplify/easy-coding-standard", + "version": "12.3.4", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2" + "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/d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2", - "reference": "d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2", + "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/polyfill-php80": "^1.15", - "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.1-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" ] }, "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": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", + "keywords": [ + "Code style", + "automation", + "fixer", + "static analysis" ], - "description": "Symfony Translation Component", - "homepage": "https://symfony.com", + "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": [ { - "url": "https://symfony.com/sponsor", + "url": "https://www.paypal.me/rectorphp", "type": "custom" }, { - "url": "https://github.com/fabpot", + "url": "https://github.com/tomasvotruba", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+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": { @@ -7877,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.8.0", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", - "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "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.9.1" + "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/" @@ -7925,19 +10371,22 @@ "check", "validate" ], - "time": "2020-04-18T12:12:48+00:00" + "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" } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "RC", "stability-flags": { - "roave/security-advisories": 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": "*", @@ -7947,5 +10396,5 @@ "platform-dev": { "ext-xdebug": "*" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.6.0" } diff --git a/docker-compose.yml b/docker-compose.yml index b9e494657..19fc0366b 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,52 +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 - - .env.local 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/etc/databases/mooc.sql b/etc/databases/mooc.sql index 6162ace70..1a094f827 100644 --- a/etc/databases/mooc.sql +++ b/etc/databases/mooc.sql @@ -1,29 +1,129 @@ -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; +/* ------------------------- + 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/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 232119235..06fda8b8b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,14 +24,19 @@ - - - ./tests + + ./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 87560ccc4..22dfbb7ba 100644 --- a/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php +++ b/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php @@ -11,23 +11,18 @@ use CodelyTv\Analytics\DomainEvents\Domain\AnalyticsDomainEventName; use CodelyTv\Analytics\DomainEvents\Domain\DomainEventsRepository; -final class DomainEventStorer +final readonly class DomainEventStorer { - private DomainEventsRepository $repository; + public function __construct(private DomainEventsRepository $repository) {} - public function __construct(DomainEventsRepository $repository) - { - $this->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 60ff5b338..5607d45ce 100644 --- a/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php +++ b/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php @@ -11,27 +11,22 @@ use CodelyTv\Shared\Domain\Bus\Event\DomainEvent; use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; -final class StoreDomainEventOnOccurred implements DomainEventSubscriber +final readonly class StoreDomainEventOnOccurred implements DomainEventSubscriber { - private DomainEventStorer $storer; - - public function __construct(DomainEventStorer $storer) - { - $this->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 dfcb26545..db3fe917e 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php @@ -4,42 +4,12 @@ namespace CodelyTv\Analytics\DomainEvents\Domain; -final class AnalyticsDomainEvent +final readonly class AnalyticsDomainEvent { - private AnalyticsDomainEventId $id; - private AnalyticsDomainEventAggregateId $aggregateId; - private AnalyticsDomainEventName $name; - private AnalyticsDomainEventBody $body; - - public function __construct( - AnalyticsDomainEventId $id, - AnalyticsDomainEventAggregateId $aggregateId, - AnalyticsDomainEventName $name, - AnalyticsDomainEventBody $body - ) { - $this->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 5b5c66a21..8d1bcdfbf 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\Uuid; -final class AnalyticsDomainEventAggregateId extends Uuid -{ -} +final class AnalyticsDomainEventAggregateId extends Uuid {} diff --git a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventBody.php b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventBody.php index 71b17d129..65bbb6c03 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventBody.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventBody.php @@ -4,17 +4,12 @@ namespace CodelyTv\Analytics\DomainEvents\Domain; -final class AnalyticsDomainEventBody +final readonly class AnalyticsDomainEventBody { - private array $value; + public function __construct(private array $value) {} - public function __construct(array $value) - { - $this->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 2e186b375..6cfebcb7a 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\Uuid; -final class AnalyticsDomainEventId extends Uuid -{ -} +final class AnalyticsDomainEventId extends Uuid {} diff --git a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventName.php b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventName.php index 36f393995..67294de35 100644 --- a/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventName.php +++ b/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventName.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class AnalyticsDomainEventName extends StringValueObject -{ -} +final class AnalyticsDomainEventName extends StringValueObject {} diff --git a/src/Analytics/DomainEvents/Domain/DomainEventsRepository.php b/src/Analytics/DomainEvents/Domain/DomainEventsRepository.php index 028572b7d..655b450d0 100644 --- a/src/Analytics/DomainEvents/Domain/DomainEventsRepository.php +++ b/src/Analytics/DomainEvents/Domain/DomainEventsRepository.php @@ -6,5 +6,5 @@ interface DomainEventsRepository { - public function save(AnalyticsDomainEvent $event): void; + public function save(AnalyticsDomainEvent $event): void; } diff --git a/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommand.php b/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommand.php index 3545e20ec..3ca7806a0 100644 --- a/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommand.php +++ b/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommand.php @@ -6,24 +6,17 @@ use CodelyTv\Shared\Domain\Bus\Command\Command; -final class AuthenticateUserCommand implements Command +final readonly class AuthenticateUserCommand implements Command { - private string $username; - private string $password; + public function __construct(private string $username, private string $password) {} - public function __construct(string $username, string $password) - { - $this->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 8d1c63e0c..6278c023d 100644 --- a/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php +++ b/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php @@ -8,20 +8,15 @@ use CodelyTv\Backoffice\Auth\Domain\AuthUsername; use CodelyTv\Shared\Domain\Bus\Command\CommandHandler; -final class AuthenticateUserCommandHandler implements CommandHandler +final readonly class AuthenticateUserCommandHandler implements CommandHandler { - private UserAuthenticator $authenticator; + public function __construct(private UserAuthenticator $authenticator) {} - public function __construct(UserAuthenticator $authenticator) - { - $this->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 edf944b7f..99bcf5dfe 100644 --- a/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php +++ b/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php @@ -11,34 +11,25 @@ use CodelyTv\Backoffice\Auth\Domain\InvalidAuthCredentials; use CodelyTv\Backoffice\Auth\Domain\InvalidAuthUsername; -final class UserAuthenticator +final readonly class UserAuthenticator { - private AuthRepository $repository; - - public function __construct(AuthRepository $repository) - { - $this->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 9953d265f..ec76483a7 100644 --- a/src/Backoffice/Auth/Domain/AuthPassword.php +++ b/src/Backoffice/Auth/Domain/AuthPassword.php @@ -8,8 +8,8 @@ final class AuthPassword extends StringValueObject { - public function isEquals(AuthPassword $other): bool - { - return $this->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 ac1bf2334..49088ecd2 100644 --- a/src/Backoffice/Auth/Domain/AuthRepository.php +++ b/src/Backoffice/Auth/Domain/AuthRepository.php @@ -6,5 +6,5 @@ interface AuthRepository { - public function search(AuthUsername $username): ?AuthUser; + public function search(AuthUsername $username): ?AuthUser; } diff --git a/src/Backoffice/Auth/Domain/AuthUser.php b/src/Backoffice/Auth/Domain/AuthUser.php index c68b91269..5be0b4b51 100644 --- a/src/Backoffice/Auth/Domain/AuthUser.php +++ b/src/Backoffice/Auth/Domain/AuthUser.php @@ -4,24 +4,17 @@ namespace CodelyTv\Backoffice\Auth\Domain; -final class AuthUser +final readonly class AuthUser { - private AuthUsername $username; - private AuthPassword $password; + public function __construct(private AuthUsername $username, private AuthPassword $password) {} - public function __construct(AuthUsername $username, AuthPassword $password) - { - $this->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 2841fa3a4..bc1f5b884 100644 --- a/src/Backoffice/Auth/Domain/AuthUsername.php +++ b/src/Backoffice/Auth/Domain/AuthUsername.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class AuthUsername extends StringValueObject -{ -} +final class AuthUsername extends StringValueObject {} diff --git a/src/Backoffice/Auth/Domain/InvalidAuthCredentials.php b/src/Backoffice/Auth/Domain/InvalidAuthCredentials.php index b5a87059f..1854118bd 100644 --- a/src/Backoffice/Auth/Domain/InvalidAuthCredentials.php +++ b/src/Backoffice/Auth/Domain/InvalidAuthCredentials.php @@ -8,8 +8,8 @@ final class InvalidAuthCredentials extends RuntimeException { - public function __construct(AuthUsername $username) - { - parent::__construct(sprintf('The credentials for <%s> 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 c00e0b5f3..b120207cb 100644 --- a/src/Backoffice/Auth/Domain/InvalidAuthUsername.php +++ b/src/Backoffice/Auth/Domain/InvalidAuthUsername.php @@ -8,8 +8,8 @@ final class InvalidAuthUsername extends RuntimeException { - public function __construct(AuthUsername $username) - { - parent::__construct(sprintf('The user <%s> 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 d1645e452..dea981f8a 100644 --- a/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php +++ b/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php @@ -8,19 +8,20 @@ use CodelyTv\Backoffice\Auth\Domain\AuthRepository; use CodelyTv\Backoffice\Auth\Domain\AuthUser; use CodelyTv\Backoffice\Auth\Domain\AuthUsername; + use function Lambdish\Phunctional\get; final class InMemoryAuthRepository implements AuthRepository { - private const USERS = [ - 'javi' => '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 041db61ae..d7984b858 100644 --- a/src/Backoffice/Courses/Application/BackofficeCourseResponse.php +++ b/src/Backoffice/Courses/Application/BackofficeCourseResponse.php @@ -4,31 +4,22 @@ namespace CodelyTv\Backoffice\Courses\Application; -final class BackofficeCourseResponse +final readonly class BackofficeCourseResponse { - private string $id; - private string $name; - private string $duration; + public function __construct(private string $id, private string $name, private string $duration) {} - public function __construct(string $id, string $name, string $duration) - { - $this->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 01a938d02..489841794 100644 --- a/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php +++ b/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php @@ -8,15 +8,15 @@ final class BackofficeCoursesResponse implements Response { - private array $courses; + private readonly array $courses; - public function __construct(BackofficeCourseResponse ...$courses) - { - $this->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 7ff1f3e2a..8493f6a46 100644 --- a/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php +++ b/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php @@ -7,17 +7,12 @@ use CodelyTv\Backoffice\Courses\Domain\BackofficeCourse; use CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository; -final class BackofficeCourseCreator +final readonly class BackofficeCourseCreator { - private BackofficeCourseRepository $repository; + public function __construct(private BackofficeCourseRepository $repository) {} - public function __construct(BackofficeCourseRepository $repository) - { - $this->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 bd29c7847..06ecd9541 100644 --- a/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php +++ b/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php @@ -7,22 +7,17 @@ use CodelyTv\Mooc\Courses\Domain\CourseCreatedDomainEvent; use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; -final class CreateBackofficeCourseOnCourseCreated implements DomainEventSubscriber +final readonly class CreateBackofficeCourseOnCourseCreated implements DomainEventSubscriber { - private BackofficeCourseCreator $creator; + public function __construct(private BackofficeCourseCreator $creator) {} - public function __construct(BackofficeCourseCreator $creator) - { - $this->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 cab20e114..8cb541d64 100644 --- a/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php +++ b/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php @@ -8,28 +8,24 @@ use CodelyTv\Backoffice\Courses\Application\BackofficeCoursesResponse; use CodelyTv\Backoffice\Courses\Domain\BackofficeCourse; use CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository; + use function Lambdish\Phunctional\map; -final class AllBackofficeCoursesSearcher +final readonly class AllBackofficeCoursesSearcher { - private BackofficeCourseRepository $repository; - - public function __construct(BackofficeCourseRepository $repository) - { - $this->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 1d537951b..41df25386 100644 --- a/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php +++ b/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\Bus\Query\Query; -final class SearchAllBackofficeCoursesQuery implements Query -{ -} +final class SearchAllBackofficeCoursesQuery implements Query {} diff --git a/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQueryHandler.php b/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQueryHandler.php index 3eee422b4..552bb73a5 100644 --- a/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQueryHandler.php +++ b/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQueryHandler.php @@ -7,17 +7,12 @@ use CodelyTv\Backoffice\Courses\Application\BackofficeCoursesResponse; use CodelyTv\Shared\Domain\Bus\Query\QueryHandler; -final class SearchAllBackofficeCoursesQueryHandler implements QueryHandler +final readonly class SearchAllBackofficeCoursesQueryHandler implements QueryHandler { - private AllBackofficeCoursesSearcher $searcher; + public function __construct(private AllBackofficeCoursesSearcher $searcher) {} - public function __construct(AllBackofficeCoursesSearcher $searcher) - { - $this->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 0cd6bab36..4ed2280a4 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php +++ b/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php @@ -11,30 +11,26 @@ use CodelyTv\Shared\Domain\Criteria\Criteria; use CodelyTv\Shared\Domain\Criteria\Filters; use CodelyTv\Shared\Domain\Criteria\Order; + use function Lambdish\Phunctional\map; -final class BackofficeCoursesByCriteriaSearcher +final readonly class BackofficeCoursesByCriteriaSearcher { - private BackofficeCourseRepository $repository; - - public function __construct(BackofficeCourseRepository $repository) - { - $this->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 52383d38b..3833cd4e5 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php +++ b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php @@ -6,50 +6,38 @@ use CodelyTv\Shared\Domain\Bus\Query\Query; -final class SearchBackofficeCoursesByCriteriaQuery implements Query +final readonly class SearchBackofficeCoursesByCriteriaQuery implements Query { - private array $filters; - private ?string $orderBy; - private ?string $order; - private ?int $limit; - private ?int $offset; - - public function __construct( - array $filters, - string $orderBy = null, - string $order = null, - int $limit = null, - int $offset = null - ) { - $this->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 36577d51e..9be731db6 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php +++ b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php @@ -9,20 +9,15 @@ use CodelyTv\Shared\Domain\Criteria\Filters; use CodelyTv\Shared\Domain\Criteria\Order; -final class SearchBackofficeCoursesByCriteriaQueryHandler implements QueryHandler +final readonly class SearchBackofficeCoursesByCriteriaQueryHandler implements QueryHandler { - private BackofficeCoursesByCriteriaSearcher $searcher; + public function __construct(private BackofficeCoursesByCriteriaSearcher $searcher) {} - public function __construct(BackofficeCoursesByCriteriaSearcher $searcher) - { - $this->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 a9cadd0f0..85bc4ea53 100644 --- a/src/Backoffice/Courses/Domain/BackofficeCourse.php +++ b/src/Backoffice/Courses/Domain/BackofficeCourse.php @@ -8,43 +8,34 @@ final class BackofficeCourse extends AggregateRoot { - private string $id; - private string $name; - private string $duration; - - public function __construct(string $id, string $name, string $duration) - { - $this->id = $id; - $this->name = $name; - $this->duration = $duration; - } - - public static function fromPrimitives(array $primitives): BackofficeCourse - { - 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; - } + 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 1f8d82c40..815a0a6b3 100644 --- a/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php @@ -8,9 +8,9 @@ interface BackofficeCourseRepository { - public function save(BackofficeCourse $course): void; + public function save(BackofficeCourse $course): void; - public function searchAll(): array; + public function searchAll(): array; - public function matching(Criteria $criteria): array; + public function matching(Criteria $criteria): array; } diff --git a/src/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepository.php b/src/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepository.php index bc06eced7..3a359ce41 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepository.php @@ -8,32 +8,33 @@ use CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository; use CodelyTv\Shared\Domain\Criteria\Criteria; use CodelyTv\Shared\Infrastructure\Persistence\Elasticsearch\ElasticsearchRepository; + use function Lambdish\Phunctional\map; final class ElasticsearchBackofficeCourseRepository extends ElasticsearchRepository implements BackofficeCourseRepository { - 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::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 f6fa776f3..d2d84c341 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/InMemoryCacheBackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Infrastructure/Persistence/InMemoryCacheBackofficeCourseRepository.php @@ -7,41 +7,38 @@ use CodelyTv\Backoffice\Courses\Domain\BackofficeCourse; use CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository; use CodelyTv\Shared\Domain\Criteria\Criteria; + use function Lambdish\Phunctional\get; final class InMemoryCacheBackofficeCourseRepository implements BackofficeCourseRepository { - private static array $allCoursesCache = []; - private static array $matchingCache = []; - private BackofficeCourseRepository $repository; - - public function __construct(BackofficeCourseRepository $repository) - { - $this->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 8203a7fee..46891200b 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php +++ b/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php @@ -12,20 +12,20 @@ final class MySqlBackofficeCourseRepository extends DoctrineRepository implements BackofficeCourseRepository { - public function save(BackofficeCourse $course): void - { - $this->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 c901cbf57..12b07bed4 100644 --- a/src/Mooc/Courses/Application/Create/CourseCreator.php +++ b/src/Mooc/Courses/Application/Create/CourseCreator.php @@ -8,25 +8,18 @@ use CodelyTv\Mooc\Courses\Domain\CourseDuration; use CodelyTv\Mooc\Courses\Domain\CourseName; use CodelyTv\Mooc\Courses\Domain\CourseRepository; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Bus\Event\EventBus; -final class CourseCreator +final readonly class CourseCreator { - private CourseRepository $repository; - private EventBus $bus; + public function __construct(private CourseRepository $repository, private EventBus $bus) {} - public function __construct(CourseRepository $repository, EventBus $bus) - { - $this->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 22e09e218..e710c86a9 100644 --- a/src/Mooc/Courses/Application/Create/CreateCourseCommand.php +++ b/src/Mooc/Courses/Application/Create/CreateCourseCommand.php @@ -6,31 +6,22 @@ use CodelyTv\Shared\Domain\Bus\Command\Command; -final class CreateCourseCommand implements Command +final readonly class CreateCourseCommand implements Command { - private string $id; - private string $name; - private string $duration; - - public function __construct(string $id, string $name, string $duration) - { - $this->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 6b0355a24..e4685c5cb 100644 --- a/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php +++ b/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php @@ -6,24 +6,19 @@ use CodelyTv\Mooc\Courses\Domain\CourseDuration; use CodelyTv\Mooc\Courses\Domain\CourseName; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Bus\Command\CommandHandler; -final class CreateCourseCommandHandler implements CommandHandler +final readonly class CreateCourseCommandHandler implements CommandHandler { - private CourseCreator $creator; + public function __construct(private CourseCreator $creator) {} - public function __construct(CourseCreator $creator) - { - $this->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 4e16e44c9..695e2aef7 100644 --- a/src/Mooc/Courses/Application/Find/CourseFinder.php +++ b/src/Mooc/Courses/Application/Find/CourseFinder.php @@ -7,25 +7,20 @@ use CodelyTv\Mooc\Courses\Domain\Course; use CodelyTv\Mooc\Courses\Domain\CourseNotExist; use CodelyTv\Mooc\Courses\Domain\CourseRepository; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; -final class CourseFinder +final readonly class CourseFinder { - private CourseRepository $repository; + public function __construct(private CourseRepository $repository) {} - public function __construct(CourseRepository $repository) - { - $this->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 356ccef0e..77c8632d4 100644 --- a/src/Mooc/Courses/Application/Update/CourseRenamer.php +++ b/src/Mooc/Courses/Application/Update/CourseRenamer.php @@ -7,29 +7,25 @@ use CodelyTv\Mooc\Courses\Application\Find\CourseFinder; use CodelyTv\Mooc\Courses\Domain\CourseName; use CodelyTv\Mooc\Courses\Domain\CourseRepository; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Bus\Event\EventBus; -final class CourseRenamer +final readonly class CourseRenamer { - private CourseRepository $repository; - private CourseFinder $finder; - private EventBus $bus; - - public function __construct(CourseRepository $repository, EventBus $bus) - { - $this->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 81f575ef2..854ecdd4e 100644 --- a/src/Mooc/Courses/Domain/Course.php +++ b/src/Mooc/Courses/Domain/Course.php @@ -4,48 +4,39 @@ namespace CodelyTv\Mooc\Courses\Domain; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Aggregate\AggregateRoot; final class Course extends AggregateRoot { - private CourseId $id; - private CourseName $name; - private CourseDuration $duration; - - public function __construct(CourseId $id, CourseName $name, CourseDuration $duration) - { - $this->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 f7562e437..69e89f799 100644 --- a/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php +++ b/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php @@ -8,51 +8,45 @@ final class CourseCreatedDomainEvent extends DomainEvent { - private string $name; - private string $duration; - - public function __construct( - string $id, - string $name, - string $duration, - string $eventId = null, - string $occurredOn = null - ) { - parent::__construct($id, $eventId, $occurredOn); - - $this->name = $name; - $this->duration = $duration; - } - - 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; - } + 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 3de808b53..71a56484b 100644 --- a/src/Mooc/Courses/Domain/CourseDuration.php +++ b/src/Mooc/Courses/Domain/CourseDuration.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class CourseDuration extends StringValueObject -{ -} +final class CourseDuration extends StringValueObject {} diff --git a/src/Mooc/Courses/Domain/CourseName.php b/src/Mooc/Courses/Domain/CourseName.php index 52855e4b2..2235c0996 100644 --- a/src/Mooc/Courses/Domain/CourseName.php +++ b/src/Mooc/Courses/Domain/CourseName.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class CourseName extends StringValueObject -{ -} +final class CourseName extends StringValueObject {} diff --git a/src/Mooc/Courses/Domain/CourseNotExist.php b/src/Mooc/Courses/Domain/CourseNotExist.php index 8f20d6202..c91f35340 100644 --- a/src/Mooc/Courses/Domain/CourseNotExist.php +++ b/src/Mooc/Courses/Domain/CourseNotExist.php @@ -4,27 +4,23 @@ namespace CodelyTv\Mooc\Courses\Domain; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\DomainError; final class CourseNotExist extends DomainError { - private CourseId $id; - - public function __construct(CourseId $id) - { - $this->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 b00bf3555..338ad9500 100644 --- a/src/Mooc/Courses/Domain/CourseRepository.php +++ b/src/Mooc/Courses/Domain/CourseRepository.php @@ -4,11 +4,11 @@ namespace CodelyTv\Mooc\Courses\Domain; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; interface CourseRepository { - public function save(Course $course): void; + public function save(Course $course): void; - public function search(CourseId $id): ?Course; + public function search(CourseId $id): ?Course; } diff --git a/src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php b/src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php new file mode 100644 index 000000000..59f78fe54 --- /dev/null +++ b/src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php @@ -0,0 +1,40 @@ +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 1a5083215..b6a15ecbd 100644 --- a/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php +++ b/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php @@ -6,26 +6,26 @@ use CodelyTv\Mooc\Courses\Domain\Course; use CodelyTv\Mooc\Courses\Domain\CourseRepository; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; final class FileCourseRepository implements CourseRepository { - private const FILE_PATH = __DIR__ . '/courses'; + private const FILE_PATH = __DIR__ . '/courses'; - public function save(Course $course): void - { - file_put_contents($this->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 bef89c7ad..000422643 100644 --- a/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php +++ b/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php @@ -7,23 +7,18 @@ use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounterNotExist; use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounterRepository; -final class CoursesCounterFinder +final readonly class CoursesCounterFinder { - private CoursesCounterRepository $repository; + public function __construct(private CoursesCounterRepository $repository) {} - public function __construct(CoursesCounterRepository $repository) - { - $this->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 f42f8f763..21e8a0811 100644 --- a/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php +++ b/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php @@ -6,17 +6,12 @@ use CodelyTv\Shared\Domain\Bus\Query\Response; -final class CoursesCounterResponse implements Response +final readonly class CoursesCounterResponse implements Response { - private int $total; + public function __construct(private int $total) {} - public function __construct(int $total) - { - $this->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 a13b1e9ea..4be728c05 100644 --- a/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php +++ b/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\Bus\Query\Query; -final class FindCoursesCounterQuery implements Query -{ -} +final class FindCoursesCounterQuery implements Query {} diff --git a/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandler.php b/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandler.php index 94b7185f0..cedb24b1c 100644 --- a/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandler.php +++ b/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandler.php @@ -6,17 +6,12 @@ use CodelyTv\Shared\Domain\Bus\Query\QueryHandler; -final class FindCoursesCounterQueryHandler implements QueryHandler +final readonly class FindCoursesCounterQueryHandler implements QueryHandler { - private CoursesCounterFinder $finder; + public function __construct(private CoursesCounterFinder $finder) {} - public function __construct(CoursesCounterFinder $finder) - { - $this->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 836b7d3e0..b1131d11d 100644 --- a/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php +++ b/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php @@ -7,40 +7,32 @@ use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounter; use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounterId; use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounterRepository; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Bus\Event\EventBus; use CodelyTv\Shared\Domain\UuidGenerator; -final class CoursesCounterIncrementer +final readonly class CoursesCounterIncrementer { - private CoursesCounterRepository $repository; - private UuidGenerator $uuidGenerator; - private EventBus $bus; - - public function __construct( - CoursesCounterRepository $repository, - UuidGenerator $uuidGenerator, - EventBus $bus - ) { - $this->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 67c7f4640..d561d7686 100644 --- a/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php +++ b/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php @@ -5,28 +5,24 @@ namespace CodelyTv\Mooc\CoursesCounter\Application\Increment; use CodelyTv\Mooc\Courses\Domain\CourseCreatedDomainEvent; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; + use function Lambdish\Phunctional\apply; -final class IncrementCoursesCounterOnCourseCreated implements DomainEventSubscriber +final readonly class IncrementCoursesCounterOnCourseCreated implements DomainEventSubscriber { - private CoursesCounterIncrementer $incrementer; - - public function __construct(CoursesCounterIncrementer $incrementer) - { - $this->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 69dc22947..3d89a5da0 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounter.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounter.php @@ -4,60 +4,60 @@ namespace CodelyTv\Mooc\CoursesCounter\Domain; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Aggregate\AggregateRoot; + use function Lambdish\Phunctional\search; final class CoursesCounter extends AggregateRoot { - private CoursesCounterTotal $total; - private array $existingCourses; - private CoursesCounterId $id; - - public function __construct(CoursesCounterId $id, CoursesCounterTotal $total, CourseId ...$existingCourses) - { - $this->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 9142b2594..16d0461e9 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\Uuid; -final class CoursesCounterId extends Uuid -{ -} +final class CoursesCounterId extends Uuid {} diff --git a/src/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEvent.php b/src/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEvent.php index 0cd06fbf3..a64681dc7 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEvent.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEvent.php @@ -8,33 +8,33 @@ final class CoursesCounterIncrementedDomainEvent extends DomainEvent { - private int $total; - - public function __construct(string $aggregateId, int $total, string $eventId = null, string $occurredOn = null) - { - parent::__construct($aggregateId, $eventId, $occurredOn); - - $this->total = $total; - } - - 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, - ]; - } + 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 5650c9267..a882cdc38 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php @@ -8,8 +8,8 @@ final class CoursesCounterNotExist extends RuntimeException { - public function __construct() - { - parent::__construct('The courses counter not exist'); - } + public function __construct() + { + parent::__construct('The courses counter not exist'); + } } diff --git a/src/Mooc/CoursesCounter/Domain/CoursesCounterRepository.php b/src/Mooc/CoursesCounter/Domain/CoursesCounterRepository.php index 4dcd67d05..d2f875b70 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterRepository.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterRepository.php @@ -6,7 +6,7 @@ interface CoursesCounterRepository { - public function save(CoursesCounter $counter): void; + public function save(CoursesCounter $counter): void; - public function search(): ?CoursesCounter; + public function search(): ?CoursesCounter; } diff --git a/src/Mooc/CoursesCounter/Domain/CoursesCounterTotal.php b/src/Mooc/CoursesCounter/Domain/CoursesCounterTotal.php index 619ca19bb..6446125fa 100644 --- a/src/Mooc/CoursesCounter/Domain/CoursesCounterTotal.php +++ b/src/Mooc/CoursesCounter/Domain/CoursesCounterTotal.php @@ -8,13 +8,13 @@ final class CoursesCounterTotal extends IntValueObject { - public static function initialize(): self - { - return new self(0); - } + public static function initialize(): self + { + return new self(0); + } - public function increment(): self - { - return new self($this->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 7a300a2bc..6a2c5d95a 100644 --- a/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php +++ b/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php @@ -9,8 +9,8 @@ final class CourseCounterIdType extends UuidType { - protected function typeClassName(): string - { - return CoursesCounterId::class; - } + protected function typeClassName(): string + { + return CoursesCounterId::class; + } } diff --git a/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseIdsType.php b/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseIdsType.php index b5da7711a..71c98ac5c 100644 --- a/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseIdsType.php +++ b/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseIdsType.php @@ -4,34 +4,34 @@ namespace CodelyTv\Mooc\CoursesCounter\Infrastructure\Persistence\Doctrine; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Infrastructure\Doctrine\Dbal\DoctrineCustomType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\JsonType; + use function Lambdish\Phunctional\map; final class CourseIdsType extends JsonType implements DoctrineCustomType { - public static function customTypeName(): string - { - return 'course_ids'; - } - - public function getName(): string - { - return self::customTypeName(); - } - - /** @var CourseId[] $value */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) - { - return parent::convertToDatabaseValue(map(fn(CourseId $id) => $id->value(), $value), $platform); - } - - public function convertToPHPValue($value, AbstractPlatform $platform) - { - $scalars = parent::convertToPHPValue($value, $platform); - - return map(fn(string $value) => new CourseId($value), $scalars); - } + 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 2de9f4ebc..64622b072 100644 --- a/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php +++ b/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php @@ -10,13 +10,13 @@ final class DoctrineCoursesCounterRepository extends DoctrineRepository implements CoursesCounterRepository { - public function save(CoursesCounter $counter): void - { - $this->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 85c2367e3..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 07d8290a9..28823de48 100644 --- a/src/Mooc/Shared/Infrastructure/Doctrine/DbalTypesSearcher.php +++ b/src/Mooc/Shared/Infrastructure/Doctrine/DbalTypesSearcher.php @@ -4,70 +4,66 @@ namespace CodelyTv\Mooc\Shared\Infrastructure\Doctrine; -use CodelyTv\Shared\Domain\Utils; use function Lambdish\Phunctional\filter; use function Lambdish\Phunctional\map; use function Lambdish\Phunctional\reduce; final class DbalTypesSearcher { - private const MAPPINGS_PATH = 'Infrastructure/Persistence/Doctrine'; + 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); + public static function inPath(string $path, string $contextName): array + { + $possibleDbalDirectories = self::possibleDbalPaths($path); + $dbalDirectories = filter(self::isExistingDbalPath(), $possibleDbalDirectories); - return reduce(self::dbalClassesSearcher($contextName), $dbalDirectories, []); - } + return reduce(self::dbalClassesSearcher($contextName), $dbalDirectories, []); + } - private static function modulesInPath(string $path): array - { - return filter( - static fn(string $possibleModule) => !in_array($possibleModule, ['.', '..']), - scandir($path) - ); - } + 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 ($unused, string $module) use ($path) { - $mappingsPath = self::MAPPINGS_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)) - ); - } + 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 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) { - $possibleFiles = scandir($path); - $files = filter( - static fn($file) => Utils::endsWith('Type.php', $file), - $possibleFiles - ); + 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) { - $fullPath = "$path/$file"; - $splittedPath = explode("/src/$contextName/", $fullPath); + $namespaces = map( + static function (string $file) use ($path, $contextName): string { + $fullPath = "$path/$file"; + $splittedPath = explode("/src/$contextName/", $fullPath); - $classWithoutPrefix = str_replace(['.php', '/'], ['', '\\'], $splittedPath[1]); + $classWithoutPrefix = str_replace(['.php', '/'], ['', '\\'], $splittedPath[1]); - return "CodelyTv\\$contextName\\$classWithoutPrefix"; - }, - $files - ); + return "CodelyTv\\$contextName\\$classWithoutPrefix"; + }, + $files + ); - return array_merge($totalNamespaces, $namespaces); - }; - } + return array_merge($totalNamespaces, $namespaces); + }; + } } diff --git a/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php b/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php index 9900a15be..85f65ed63 100644 --- a/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php +++ b/src/Mooc/Shared/Infrastructure/Doctrine/DoctrinePrefixesSearcher.php @@ -10,43 +10,43 @@ final class DoctrinePrefixesSearcher { - 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) => !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 4153955e0..71f0b4566 100644 --- a/src/Mooc/Shared/Infrastructure/Doctrine/MoocEntityManagerFactory.php +++ b/src/Mooc/Shared/Infrastructure/Doctrine/MoocEntityManagerFactory.php @@ -9,25 +9,25 @@ final class MoocEntityManagerFactory { - private const SCHEMA_PATH = __DIR__ . '/../../../../../etc/databases/mooc.sql'; - - public static function create(array $parameters, string $environment): EntityManagerInterface - { - $isDevMode = 'prod' !== $environment; - - $prefixes = array_merge( - DoctrinePrefixesSearcher::inPath(__DIR__ . '/../../../../Mooc', 'CodelyTv\Mooc'), - DoctrinePrefixesSearcher::inPath(__DIR__ . '/../../../../Backoffice', 'CodelyTv\Backoffice') - ); - - $dbalCustomTypesClasses = DbalTypesSearcher::inPath(__DIR__ . '/../../../../Mooc', 'Mooc'); - - return DoctrineEntityManagerFactory::create( - $parameters, - $prefixes, - $isDevMode, - self::SCHEMA_PATH, - $dbalCustomTypesClasses - ); - } + private const SCHEMA_PATH = __DIR__ . '/../../../../../etc/databases/mooc.sql'; + + public static function create(array $parameters, string $environment): EntityManagerInterface + { + $isDevMode = $environment !== 'prod'; + + $prefixes = array_merge( + DoctrinePrefixesSearcher::inPath(__DIR__ . '/../../../../Mooc', 'CodelyTv\Mooc'), + DoctrinePrefixesSearcher::inPath(__DIR__ . '/../../../../Backoffice', 'CodelyTv\Backoffice') + ); + + $dbalCustomTypesClasses = DbalTypesSearcher::inPath(__DIR__ . '/../../../../Mooc', 'Mooc'); + + return DoctrineEntityManagerFactory::create( + $parameters, + $prefixes, + $isDevMode, + self::SCHEMA_PATH, + $dbalCustomTypesClasses + ); + } } diff --git a/src/Mooc/Shared/Infrastructure/Symfony/DependencyInjection/mooc_services.yaml b/src/Mooc/Shared/Infrastructure/Symfony/DependencyInjection/mooc_services.yaml index 199b41015..fa4f5522b 100644 --- a/src/Mooc/Shared/Infrastructure/Symfony/DependencyInjection/mooc_services.yaml +++ b/src/Mooc/Shared/Infrastructure/Symfony/DependencyInjection/mooc_services.yaml @@ -1,13 +1,13 @@ services: # Databases Doctrine\ORM\EntityManager: - factory: [CodelyTv\Mooc\Shared\Infrastructure\Doctrine\MoocEntityManagerFactory, create] + 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)%' + - 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: @@ -17,4 +17,3 @@ services: # Courses CodelyTv\Mooc\Courses\Domain\CourseRepository: '@CodelyTv\Mooc\Courses\Infrastructure\Persistence\DoctrineCourseRepository' - diff --git a/src/Mooc/Steps/Application/Create/CreateVideoStepCommandHandler.php b/src/Mooc/Steps/Application/Create/CreateVideoStepCommandHandler.php new file mode 100644 index 000000000..34c301890 --- /dev/null +++ b/src/Mooc/Steps/Application/Create/CreateVideoStepCommandHandler.php @@ -0,0 +1,14 @@ +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 80fa7ddbd..8162916cf 100644 --- a/src/Shared/Domain/Aggregate/AggregateRoot.php +++ b/src/Shared/Domain/Aggregate/AggregateRoot.php @@ -8,18 +8,18 @@ abstract class AggregateRoot { - private array $domainEvents = []; + private array $domainEvents = []; - final public function pullDomainEvents(): array - { - $domainEvents = $this->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 92817d74a..d1c5f6e2d 100644 --- a/src/Shared/Domain/Assert.php +++ b/src/Shared/Domain/Assert.php @@ -8,19 +8,17 @@ final class Assert { - public static function arrayOf(string $class, array $items): void - { - foreach ($items as $item) { - self::instanceOf($class, $item); - } - } + public static function arrayOf(string $class, array $items): void + { + foreach ($items as $item) { + self::instanceOf($class, $item); + } + } - public static function instanceOf($class, $item): void - { - if (!$item instanceof $class) { - throw new InvalidArgumentException( - sprintf('The object <%s> is not an instance of <%s>', $class, get_class($item)) - ); - } - } + 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 d8343b898..dceac12c7 100644 --- a/src/Shared/Domain/Bus/Command/Command.php +++ b/src/Shared/Domain/Bus/Command/Command.php @@ -4,6 +4,4 @@ namespace CodelyTv\Shared\Domain\Bus\Command; -interface Command -{ -} +interface Command {} diff --git a/src/Shared/Domain/Bus/Command/CommandBus.php b/src/Shared/Domain/Bus/Command/CommandBus.php index 2c82ef796..fdf5e9ef9 100644 --- a/src/Shared/Domain/Bus/Command/CommandBus.php +++ b/src/Shared/Domain/Bus/Command/CommandBus.php @@ -6,5 +6,5 @@ interface CommandBus { - public function dispatch(Command $command): void; + public function dispatch(Command $command): void; } diff --git a/src/Shared/Domain/Bus/Command/CommandHandler.php b/src/Shared/Domain/Bus/Command/CommandHandler.php index 2cea0d72d..1168c1df4 100644 --- a/src/Shared/Domain/Bus/Command/CommandHandler.php +++ b/src/Shared/Domain/Bus/Command/CommandHandler.php @@ -4,6 +4,4 @@ namespace CodelyTv\Shared\Domain\Bus\Command; -interface CommandHandler -{ -} +interface CommandHandler {} diff --git a/src/Shared/Domain/Bus/Event/DomainEvent.php b/src/Shared/Domain/Bus/Event/DomainEvent.php index 0f7b80cf0..ffc7b0641 100644 --- a/src/Shared/Domain/Bus/Event/DomainEvent.php +++ b/src/Shared/Domain/Bus/Event/DomainEvent.php @@ -5,45 +5,43 @@ namespace CodelyTv\Shared\Domain\Bus\Event; use CodelyTv\Shared\Domain\Utils; -use CodelyTv\Shared\Domain\ValueObject\Uuid; +use CodelyTv\Shared\Domain\ValueObject\SimpleUuid; use DateTimeImmutable; abstract class DomainEvent { - private string $aggregateId; - private string $eventId; - private string $occurredOn; - - public function __construct(string $aggregateId, string $eventId = null, string $occurredOn = null) - { - $this->aggregateId = $aggregateId; - $this->eventId = $eventId ?: Uuid::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; - - 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 54903808c..a94a22bc9 100644 --- a/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php +++ b/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php @@ -6,5 +6,5 @@ interface DomainEventSubscriber { - public static function subscribedTo(): array; + public static function subscribedTo(): array; } diff --git a/src/Shared/Domain/Bus/Event/EventBus.php b/src/Shared/Domain/Bus/Event/EventBus.php index 164f77e9b..3be5d9782 100644 --- a/src/Shared/Domain/Bus/Event/EventBus.php +++ b/src/Shared/Domain/Bus/Event/EventBus.php @@ -6,5 +6,5 @@ interface EventBus { - public function publish(DomainEvent ...$events): void; + public function publish(DomainEvent ...$events): void; } diff --git a/src/Shared/Domain/Bus/Query/Query.php b/src/Shared/Domain/Bus/Query/Query.php index 319bac80b..3ed4d1467 100644 --- a/src/Shared/Domain/Bus/Query/Query.php +++ b/src/Shared/Domain/Bus/Query/Query.php @@ -4,6 +4,4 @@ namespace CodelyTv\Shared\Domain\Bus\Query; -interface Query -{ -} +interface Query {} diff --git a/src/Shared/Domain/Bus/Query/QueryBus.php b/src/Shared/Domain/Bus/Query/QueryBus.php index dc74a4b9b..8abee3634 100644 --- a/src/Shared/Domain/Bus/Query/QueryBus.php +++ b/src/Shared/Domain/Bus/Query/QueryBus.php @@ -6,5 +6,5 @@ interface QueryBus { - public function ask(Query $query): ?Response; + public function ask(Query $query): ?Response; } diff --git a/src/Shared/Domain/Bus/Query/QueryHandler.php b/src/Shared/Domain/Bus/Query/QueryHandler.php index dc4a36392..f849e7210 100644 --- a/src/Shared/Domain/Bus/Query/QueryHandler.php +++ b/src/Shared/Domain/Bus/Query/QueryHandler.php @@ -4,6 +4,4 @@ namespace CodelyTv\Shared\Domain\Bus\Query; -interface QueryHandler -{ -} +interface QueryHandler {} diff --git a/src/Shared/Domain/Bus/Query/Response.php b/src/Shared/Domain/Bus/Query/Response.php index 3bf921ee0..dd8be87cb 100644 --- a/src/Shared/Domain/Bus/Query/Response.php +++ b/src/Shared/Domain/Bus/Query/Response.php @@ -4,6 +4,4 @@ namespace CodelyTv\Shared\Domain\Bus\Query; -interface Response -{ -} +interface Response {} diff --git a/src/Shared/Domain/Collection.php b/src/Shared/Domain/Collection.php index a3e581b0f..c0b664aa8 100644 --- a/src/Shared/Domain/Collection.php +++ b/src/Shared/Domain/Collection.php @@ -7,38 +7,30 @@ use ArrayIterator; use Countable; use IteratorAggregate; -use function Lambdish\Phunctional\each; +use Traversable; +/** @template-implements IteratorAggregate*/ 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 649442dc6..876c9f441 100644 --- a/src/Shared/Domain/Criteria/Criteria.php +++ b/src/Shared/Domain/Criteria/Criteria.php @@ -4,64 +4,58 @@ namespace CodelyTv\Shared\Domain\Criteria; -final class Criteria +final readonly class Criteria { - private Filters $filters; - private Order $order; - private ?int $offset; - private ?int $limit; - - public function __construct(Filters $filters, Order $order, ?int $offset, ?int $limit) - { - $this->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 3d77b1916..33f15c365 100644 --- a/src/Shared/Domain/Criteria/Filter.php +++ b/src/Shared/Domain/Criteria/Filter.php @@ -4,45 +4,40 @@ namespace CodelyTv\Shared\Domain\Criteria; -final class Filter +final readonly class Filter { - private FilterField $field; - private FilterOperator $operator; - private FilterValue $value; - - public function __construct(FilterField $field, FilterOperator $operator, FilterValue $value) - { - $this->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 7c952bbb5..fcc11e2de 100644 --- a/src/Shared/Domain/Criteria/FilterField.php +++ b/src/Shared/Domain/Criteria/FilterField.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class FilterField extends StringValueObject -{ -} +final class FilterField extends StringValueObject {} diff --git a/src/Shared/Domain/Criteria/FilterOperator.php b/src/Shared/Domain/Criteria/FilterOperator.php index 319318590..3bd946967 100644 --- a/src/Shared/Domain/Criteria/FilterOperator.php +++ b/src/Shared/Domain/Criteria/FilterOperator.php @@ -4,36 +4,17 @@ namespace CodelyTv\Shared\Domain\Criteria; -use CodelyTv\Shared\Domain\ValueObject\Enum; -use InvalidArgumentException; - -/** - * @method static FilterOperator gt() - * @method static FilterOperator lt() - * @method static FilterOperator like() - */ -final class FilterOperator extends Enum +enum FilterOperator: string { - public const EQUAL = '='; - public const NOT_EQUAL = '!='; - public const GT = '>'; - public const LT = '<'; - public const CONTAINS = 'CONTAINS'; - public const NOT_CONTAINS = 'NOT_CONTAINS'; - private static array $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 08151b09e..5576c10fb 100644 --- a/src/Shared/Domain/Criteria/FilterValue.php +++ b/src/Shared/Domain/Criteria/FilterValue.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class FilterValue extends StringValueObject -{ -} +final class FilterValue extends StringValueObject {} diff --git a/src/Shared/Domain/Criteria/Filters.php b/src/Shared/Domain/Criteria/Filters.php index 1dc56b3ce..bea6863e3 100644 --- a/src/Shared/Domain/Criteria/Filters.php +++ b/src/Shared/Domain/Criteria/Filters.php @@ -5,41 +5,42 @@ namespace CodelyTv\Shared\Domain\Criteria; use CodelyTv\Shared\Domain\Collection; + use function Lambdish\Phunctional\reduce; final class Filters extends Collection { - 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::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) => sprintf('%s^%s', $accumulate, $filter->serialize()), - $this->items(), - '' - ); - } - - protected function type(): string - { - return Filter::class; - } + 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 2ab172180..dfbc8e016 100644 --- a/src/Shared/Domain/Criteria/Order.php +++ b/src/Shared/Domain/Criteria/Order.php @@ -4,49 +4,45 @@ namespace CodelyTv\Shared\Domain\Criteria; -final class Order +final readonly class Order { - private OrderBy $orderBy; - private OrderType $orderType; - - public function __construct(OrderBy $orderBy, OrderType $orderType) - { - $this->orderBy = $orderBy; - $this->orderType = $orderType; - } - - public static function createDesc(OrderBy $orderBy): Order - { - return new self($orderBy, OrderType::desc()); - } - - public static function fromValues(?string $orderBy, ?string $order): Order - { - return null === $orderBy ? self::none() : new Order(new OrderBy($orderBy), new OrderType($order)); - } - - public static function none(): Order - { - return new Order(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()); - } + 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 c6e43e6b9..d2d054cae 100644 --- a/src/Shared/Domain/Criteria/OrderBy.php +++ b/src/Shared/Domain/Criteria/OrderBy.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\ValueObject\StringValueObject; -final class OrderBy extends StringValueObject -{ -} +final class OrderBy extends StringValueObject {} diff --git a/src/Shared/Domain/Criteria/OrderType.php b/src/Shared/Domain/Criteria/OrderType.php index 7d46d0b83..851bc0c7f 100644 --- a/src/Shared/Domain/Criteria/OrderType.php +++ b/src/Shared/Domain/Criteria/OrderType.php @@ -4,27 +4,14 @@ namespace CodelyTv\Shared\Domain\Criteria; -use CodelyTv\Shared\Domain\ValueObject\Enum; -use InvalidArgumentException; - -/** - * @method static OrderType asc() - * @method static OrderType desc() - * @method static OrderType none() - */ -final class OrderType extends Enum +enum OrderType: string { - public const ASC = 'asc'; - public const DESC = 'desc'; - public const NONE = 'none'; - - public function isNone(): bool - { - return $this->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 8ecd4725f..18eec3f93 100644 --- a/src/Shared/Domain/DomainError.php +++ b/src/Shared/Domain/DomainError.php @@ -8,12 +8,12 @@ abstract class DomainError extends DomainException { - public function __construct() - { - parent::__construct($this->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 37100e698..fbaa7710c 100644 --- a/src/Shared/Domain/Logger.php +++ b/src/Shared/Domain/Logger.php @@ -6,9 +6,9 @@ interface Logger { - public function info(string $message, array $context = []): void; + public function info(string $message, array $context = []): void; - public function warning(string $message, array $context = []): void; + public function warning(string $message, array $context = []): void; - public function critical(string $message, array $context = []): void; + public function critical(string $message, array $context = []): void; } diff --git a/src/Shared/Domain/Monitoring.php b/src/Shared/Domain/Monitoring.php index cb831294e..2602a0f40 100644 --- a/src/Shared/Domain/Monitoring.php +++ b/src/Shared/Domain/Monitoring.php @@ -6,13 +6,13 @@ interface Monitoring { - public function incrementCounter(int $times): void; + public function incrementCounter(int $times): void; - public function incrementGauge(int $times): void; + public function incrementGauge(int $times): void; - public function decrementGauge(int $times): void; + public function decrementGauge(int $times): void; - public function setGauge(int $value): void; + public function setGauge(int $value): void; - public function observeHistogram(int $value, array $labels = []): void; + public function observeHistogram(int $value, array $labels = []): void; } diff --git a/src/Shared/Domain/RandomNumberGenerator.php b/src/Shared/Domain/RandomNumberGenerator.php index fb8462781..6fe49b715 100644 --- a/src/Shared/Domain/RandomNumberGenerator.php +++ b/src/Shared/Domain/RandomNumberGenerator.php @@ -6,5 +6,5 @@ interface RandomNumberGenerator { - public function generate(): int; + public function generate(): int; } diff --git a/src/Shared/Domain/Second.php b/src/Shared/Domain/Second.php new file mode 100644 index 000000000..6ff9f4bcb --- /dev/null +++ b/src/Shared/Domain/Second.php @@ -0,0 +1,9 @@ +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 34a3c48ca..92a9d1117 100644 --- a/src/Shared/Domain/Utils.php +++ b/src/Shared/Domain/Utils.php @@ -6,84 +6,68 @@ use DateTimeImmutable; use DateTimeInterface; -use ReflectionClass; -use RuntimeException; use function Lambdish\Phunctional\filter; final class Utils { - public static function endsWith(string $needle, string $haystack): bool - { - $length = strlen($needle); - if ($length === 0) { - return true; - } - - return (substr($haystack, -$length) === $needle); - } - - 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); - } - - 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 filesIn(string $path, $fileType): array - { - return filter( - static fn(string $possibleModule) => strstr($possibleModule, $fileType), - scandir($path) - ); - } - - public static function extractClassName(object $object): string - { - $reflect = new ReflectionClass($object); - - return $reflect->getShortName(); - } + 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 313b7c249..1064a6edc 100644 --- a/src/Shared/Domain/UuidGenerator.php +++ b/src/Shared/Domain/UuidGenerator.php @@ -6,5 +6,5 @@ interface UuidGenerator { - public function generate(): string; + public function generate(): string; } diff --git a/src/Shared/Domain/ValueObject/Enum.php b/src/Shared/Domain/ValueObject/Enum.php deleted file mode 100644 index 6d96dbe96..000000000 --- a/src/Shared/Domain/ValueObject/Enum.php +++ /dev/null @@ -1,84 +0,0 @@ -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 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 value() - { - return $this->value; - } - - public function equals(Enum $other): bool - { - return $other == $this; - } - - public function __toString(): string - { - return (string) $this->value(); - } - - private function ensureIsBetweenAcceptedValues($value): void - { - if (!in_array($value, static::values(), true)) { - $this->throwExceptionForInvalidValue($value); - } - } -} diff --git a/src/Shared/Domain/ValueObject/IntValueObject.php b/src/Shared/Domain/ValueObject/IntValueObject.php index f34a11ac9..77e13cd76 100644 --- a/src/Shared/Domain/ValueObject/IntValueObject.php +++ b/src/Shared/Domain/ValueObject/IntValueObject.php @@ -6,20 +6,15 @@ abstract class IntValueObject { - protected int $value; + public function __construct(protected int $value) {} - public function __construct(int $value) - { - $this->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 a69a65c5e..63a30fcc8 100644 --- a/src/Shared/Domain/ValueObject/Uuid.php +++ b/src/Shared/Domain/ValueObject/Uuid.php @@ -6,42 +6,39 @@ use InvalidArgumentException; use Ramsey\Uuid\Uuid as RamseyUuid; +use Stringable; -class Uuid +abstract class Uuid implements Stringable { - protected string $value; - - public function __construct(string $value) - { - $this->ensureIsValidUuid($value); - - $this->value = $value; - } - - public static function random(): self - { - return new static(RamseyUuid::uuid4()->toString()); - } - - public function value(): string - { - return $this->value; - } - - public function equals(Uuid $other): bool - { - return $this->value() === $other->value(); - } - - public function __toString(): string - { - return $this->value(); - } - - private function ensureIsValidUuid($id): void - { - if (!RamseyUuid::isValid($id)) { - throw new InvalidArgumentException(sprintf('<%s> does not allow the value <%s>.', static::class, $id)); - } - } + 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 55049ca4b..23564a9e1 100644 --- a/src/Shared/Infrastructure/Bus/CallableFirstParameterExtractor.php +++ b/src/Shared/Infrastructure/Bus/CallableFirstParameterExtractor.php @@ -5,66 +5,76 @@ namespace CodelyTv\Shared\Infrastructure\Bus; use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; +use LogicException; use ReflectionClass; use ReflectionMethod; +use ReflectionNamedType; + use function Lambdish\Phunctional\map; use function Lambdish\Phunctional\reduce; use function Lambdish\Phunctional\reindex; final class CallableFirstParameterExtractor { - 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(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 function extract($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 - { - return $method->getParameters()[0]->getClass()->getName(); - } - - private function hasOnlyOneParameter(ReflectionMethod $method): bool - { - return $method->getNumberOfParameters() === 1; - } + 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 7d18fd696..fdb18e189 100644 --- a/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php +++ b/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php @@ -9,11 +9,10 @@ final class CommandNotRegisteredError extends RuntimeException { - public function __construct(Command $command) - { - $commandClass = get_class($command); + public function __construct(Command $command) + { + $commandClass = $command::class; - parent::__construct("The command <$commandClass> 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 257b6fd3e..4f6bbed0e 100644 --- a/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php +++ b/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php @@ -15,27 +15,27 @@ final class InMemorySymfonyCommandBus implements CommandBus { - private MessageBus $bus; + private readonly MessageBus $bus; - public function __construct(iterable $commandHandlers) - { - $this->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 c5e3a15ba..3d73fa61b 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php @@ -6,32 +6,22 @@ use CodelyTv\Shared\Domain\Bus\Event\DomainEvent; use CodelyTv\Shared\Domain\Utils; -use RuntimeException; -final class DomainEventJsonDeserializer +final readonly class DomainEventJsonDeserializer { - private DomainEventMapping $mapping; - - public function __construct(DomainEventMapping $mapping) - { - $this->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 011a1ff86..293e5f996 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php @@ -8,18 +8,18 @@ final class DomainEventJsonSerializer { - 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' => [], - ] - ); - } + 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 687551d19..697acad8d 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php @@ -6,45 +6,38 @@ use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; use RuntimeException; + use function Lambdish\Phunctional\reduce; use function Lambdish\Phunctional\reindex; final class DomainEventMapping { - private $mapping; - - public function __construct(iterable $mapping) - { - $this->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 c35ac9523..d39aa7651 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberLocator.php +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberLocator.php @@ -9,41 +9,42 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqQueueNameFormatter; use RuntimeException; use Traversable; + use function Lambdish\Phunctional\search; final class DomainEventSubscriberLocator { - private array $mapping; - - public function __construct(Traversable $mapping) - { - $this->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 ecd59bc34..db19cb695 100644 --- a/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php +++ b/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php @@ -14,28 +14,26 @@ class InMemorySymfonyEventBus implements EventBus { - private MessageBus $bus; + private readonly MessageBus $bus; - public function __construct(iterable $subscribers) - { - $this->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 74e976547..a07e3b09f 100644 --- a/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineDomainEventsConsumer.php +++ b/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineDomainEventsConsumer.php @@ -8,63 +8,61 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventMapping; use DateTimeImmutable; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\FetchMode; use Doctrine\ORM\EntityManager; use RuntimeException; + use function Lambdish\Phunctional\each; use function Lambdish\Phunctional\map; -final class MySqlDoctrineDomainEventsConsumer +final readonly class MySqlDoctrineDomainEventsConsumer { - private Connection $connection; - private DomainEventMapping $eventMapping; + private Connection $connection; - public function __construct(EntityManager $entityManager, DomainEventMapping $eventMapping) - { - $this->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 e34059237..2b21c3897 100644 --- a/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php +++ b/src/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBus.php @@ -9,40 +9,41 @@ use CodelyTv\Shared\Domain\Utils; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManager; + use function Lambdish\Phunctional\each; final class MySqlDoctrineEventBus implements EventBus { - private const DATABASE_TIMESTAMP_FORMAT = 'Y-m-d H:i:s'; - private Connection $connection; - - public function __construct(EntityManager $entityManager) - { - $this->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 9f1a365ff..ea3bcb9fd 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConnection.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConnection.php @@ -11,58 +11,58 @@ final class RabbitMqConnection { - private static ?AMQPConnection $connection = null; - private static ?AMQPChannel $channel = null; - /** @var AMQPExchange[] */ - private static array $exchanges = []; - /** @var AMQPQueue[] */ - private static array $queues = []; - private array $configuration; - - public function __construct(array $configuration) - { - $this->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 16a727eaf..ea18ec7ce 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.php @@ -7,95 +7,87 @@ use AMQPEnvelope; use AMQPQueue; use AMQPQueueException; +use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventJsonDeserializer; use Throwable; + use function Lambdish\Phunctional\assoc; use function Lambdish\Phunctional\get; -final class RabbitMqDomainEventsConsumer +final readonly class RabbitMqDomainEventsConsumer { - private RabbitMqConnection $connection; - private DomainEventJsonDeserializer $deserializer; - private string $exchangeName; - private int $maxRetries; - - public function __construct( - RabbitMqConnection $connection, - DomainEventJsonDeserializer $deserializer, - string $exchangeName, - int $maxRetries - ) { - $this->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 48c278745..1d7bc1a89 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.php @@ -9,55 +9,48 @@ use CodelyTv\Shared\Domain\Bus\Event\EventBus; use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventJsonSerializer; use CodelyTv\Shared\Infrastructure\Bus\Event\MySql\MySqlDoctrineEventBus; + use function Lambdish\Phunctional\each; -final class RabbitMqEventBus implements EventBus +final readonly class RabbitMqEventBus implements EventBus { - private RabbitMqConnection $connection; - private string $exchangeName; - private MySqlDoctrineEventBus $failoverPublisher; - - public function __construct( - RabbitMqConnection $connection, - string $exchangeName, - MySqlDoctrineEventBus $failoverPublisher - ) { - $this->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 db78767b7..7e9be1dc9 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php @@ -6,13 +6,13 @@ final class RabbitMqExchangeNameFormatter { - public static function retry(string $exchangeName): string - { - return "retry-$exchangeName"; - } + public static function retry(string $exchangeName): string + { + return "retry-$exchangeName"; + } - public static function deadLetter(string $exchangeName): string - { - return "dead_letter-$exchangeName"; - } + public static function deadLetter(string $exchangeName): string + { + return "dead_letter-$exchangeName"; + } } diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqQueueNameFormatter.php b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqQueueNameFormatter.php index 312c31a4b..052380602 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqQueueNameFormatter.php +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqQueueNameFormatter.php @@ -6,48 +6,49 @@ use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; use CodelyTv\Shared\Domain\Utils; + use function Lambdish\Phunctional\last; use function Lambdish\Phunctional\map; final class RabbitMqQueueNameFormatter { - public static function format(DomainEventSubscriber $subscriber): string - { - $subscriberClassPaths = explode('\\', str_replace('CodelyTv', 'codelytv', get_class($subscriber))); + public static function format(DomainEventSubscriber $subscriber): string + { + $subscriberClassPaths = explode('\\', str_replace('CodelyTv', 'codelytv', $subscriber::class)); - $queueNameParts = [ - $subscriberClassPaths[0], - $subscriberClassPaths[1], - $subscriberClassPaths[2], - last($subscriberClassPaths), - ]; + $queueNameParts = [ + $subscriberClassPaths[0], + $subscriberClassPaths[1], + $subscriberClassPaths[2], + last($subscriberClassPaths), + ]; - return implode('.', map(self::toSnakeCase(), $queueNameParts)); - } + return implode('.', map(self::toSnakeCase(), $queueNameParts)); + } - public static function formatRetry(DomainEventSubscriber $subscriber): string - { - $queueName = self::format($subscriber); + public static function formatRetry(DomainEventSubscriber $subscriber): string + { + $queueName = self::format($subscriber); - return "retry.$queueName"; - } + return "retry.$queueName"; + } - public static function formatDeadLetter(DomainEventSubscriber $subscriber): string - { - $queueName = self::format($subscriber); + public static function formatDeadLetter(DomainEventSubscriber $subscriber): string + { + $queueName = self::format($subscriber); - return "dead_letter.$queueName"; - } + return "dead_letter.$queueName"; + } - public static function shortFormat(DomainEventSubscriber $subscriber): string - { - $subscriberCamelCaseName = (string) last(explode('\\', get_class($subscriber))); + public static function shortFormat(DomainEventSubscriber $subscriber): string + { + $subscriberCamelCaseName = (string) last(explode('\\', $subscriber::class)); - return Utils::toSnakeCase($subscriberCamelCaseName); - } + return Utils::toSnakeCase($subscriberCamelCaseName); + } - private static function toSnakeCase(): callable - { - return static fn(string $text) => 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 bf00586b9..4812214d0 100644 --- a/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php +++ b/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBus.php @@ -14,30 +14,28 @@ use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; use Symfony\Component\Messenger\Stamp\HandledStamp; -final class InMemorySymfonyQueryBus implements QueryBus +final readonly class InMemorySymfonyQueryBus implements QueryBus { - private MessageBus $bus; + private MessageBus $bus; - public function __construct(iterable $queryHandlers) - { - $this->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 c7464bf01..c79d4859c 100644 --- a/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php +++ b/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php @@ -9,10 +9,10 @@ final class QueryNotRegisteredError extends RuntimeException { - public function __construct(Query $query) - { - $queryClass = get_class($query); + public function __construct(Query $query) + { + $queryClass = $query::class; - parent::__construct("The query <$queryClass> 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(fn(EntityManager $entityManager) => $entityManager->clear(), $this->connections); - } - - public function truncate(): void - { - apply(new DatabaseCleaner(), array_values($this->connections)); - } + 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 b671836cb..e76a46c3b 100644 --- a/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php +++ b/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php @@ -5,25 +5,28 @@ namespace CodelyTv\Shared\Infrastructure\Doctrine\Dbal; use Doctrine\DBAL\Types\Type; + use function Lambdish\Phunctional\each; final class DbalCustomTypesRegistrar { - private static bool $initialized = false; - - public static function register(array $customTypeClassNames): void - { - if (!self::$initialized) { - each(self::registerType(), $customTypeClassNames); - - self::$initialized = true; - } - } - - private static function registerType(): callable - { - return static function (string $customTypeClassName): void { - Type::addType($customTypeClassName::customTypeName(), $customTypeClassName); - }; - } + private static bool $initialized = false; + + public static function register(array $customTypeClassNames): void + { + if (!self::$initialized) { + each(self::registerType(), $customTypeClassNames); + + self::$initialized = true; + } + } + + private static function registerType(): callable + { + return static function (mixed $customTypeClassName): void { + $name = $customTypeClassName::customTypeName(); + + Type::addType($name, $customTypeClassName); + }; + } } diff --git a/src/Shared/Infrastructure/Doctrine/Dbal/DoctrineCustomType.php b/src/Shared/Infrastructure/Doctrine/Dbal/DoctrineCustomType.php index 38e761666..9a6cde085 100644 --- a/src/Shared/Infrastructure/Doctrine/Dbal/DoctrineCustomType.php +++ b/src/Shared/Infrastructure/Doctrine/Dbal/DoctrineCustomType.php @@ -6,5 +6,5 @@ interface DoctrineCustomType { - public static function customTypeName(): string; + public static function customTypeName(): string; } diff --git a/src/Shared/Infrastructure/Doctrine/DoctrineEntityManagerFactory.php b/src/Shared/Infrastructure/Doctrine/DoctrineEntityManagerFactory.php index db71e85f0..a19a5609e 100644 --- a/src/Shared/Infrastructure/Doctrine/DoctrineEntityManagerFactory.php +++ b/src/Shared/Infrastructure/Doctrine/DoctrineEntityManagerFactory.php @@ -5,74 +5,87 @@ namespace CodelyTv\Shared\Infrastructure\Doctrine; use CodelyTv\Shared\Infrastructure\Doctrine\Dbal\DbalCustomTypesRegistrar; -use Doctrine\Common\Cache\ArrayCache; +use Doctrine\Common\EventManager; use Doctrine\DBAL\DriverManager; -use Doctrine\DBAL\Schema\MySqlSchemaManager; +use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Schema\MySQLSchemaManager; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; -use Doctrine\ORM\Tools\Setup; +use Doctrine\ORM\ORMSetup; use RuntimeException; + use function Lambdish\Phunctional\dissoc; final class DoctrineEntityManagerFactory { - 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) { - 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 c3a99e2c3..41480e009 100644 --- a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClient.php +++ b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClient.php @@ -6,35 +6,28 @@ use Elasticsearch\Client; -final class ElasticsearchClient +final readonly class ElasticsearchClient { - private Client $client; - private string $indexPrefix; - - public function __construct(Client $client, string $indexPrefix) - { - $this->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 1111c271c..a5472afb9 100644 --- a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClientFactory.php +++ b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchClientFactory.php @@ -11,46 +11,50 @@ final class ElasticsearchClientFactory { - 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, - $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 46a8618d6..1e0928028 100644 --- a/src/Shared/Infrastructure/Logger/MonologLogger.php +++ b/src/Shared/Infrastructure/Logger/MonologLogger.php @@ -6,27 +6,22 @@ use CodelyTv\Shared\Domain\Logger; -final class MonologLogger implements Logger +final readonly class MonologLogger implements Logger { - private \Monolog\Logger $logger; - - public function __construct(\Monolog\Logger $logger) - { - $this->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 cea3bfd88..88bd57083 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineCriteriaConverter.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineCriteriaConverter.php @@ -12,93 +12,88 @@ use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\CompositeExpression; -final class DoctrineCriteriaConverter +final readonly class DoctrineCriteriaConverter { - private Criteria $criteria; - private array $criteriaToDoctrineFields; - private array $hydrators; - - public function __construct(Criteria $criteria, array $criteriaToDoctrineFields = [], array $hydrators = []) - { - $this->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(); - } - - 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) - { - 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 293903e93..dc6cdd9bb 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php @@ -7,35 +7,40 @@ use CodelyTv\Shared\Domain\Aggregate\AggregateRoot; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Exception\NotSupported; abstract class DoctrineRepository { - private EntityManager $entityManager; - - public function __construct(EntityManager $entityManager) - { - $this->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 5353a8716..1ef8213bc 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php @@ -9,32 +9,33 @@ use CodelyTv\Shared\Infrastructure\Doctrine\Dbal\DoctrineCustomType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\StringType; + use function Lambdish\Phunctional\last; abstract class UuidType extends StringType implements DoctrineCustomType { - abstract protected function typeClassName(): string; - - public function getName(): string - { - return self::customTypeName(); - } - - public static function customTypeName(): string - { - return Utils::toSnakeCase(str_replace('Type', '', last(explode('\\', static::class)))); - } - - public function convertToPHPValue($value, AbstractPlatform $platform) - { - $className = $this->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 ddcd78308..885066f5e 100644 --- a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticQueryGenerator.php +++ b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticQueryGenerator.php @@ -6,53 +6,47 @@ use CodelyTv\Shared\Domain\Criteria\Filter; use CodelyTv\Shared\Domain\Criteria\FilterOperator; -use function Lambdish\Phunctional\get; final class ElasticQueryGenerator { - 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 $termMapping = [ - FilterOperator::EQUAL => 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 bd8a5373b..257e531b3 100644 --- a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php +++ b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php @@ -5,42 +5,49 @@ namespace CodelyTv\Shared\Infrastructure\Persistence\Elasticsearch; use CodelyTv\Shared\Domain\Criteria\Criteria; + use function Lambdish\Phunctional\reduce; final class ElasticsearchCriteriaConverter { - 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 []; - } + 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 082d4cd4d..3236dd248 100644 --- a/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchRepository.php +++ b/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchRepository.php @@ -7,59 +7,55 @@ use CodelyTv\Shared\Domain\Criteria\Criteria; use CodelyTv\Shared\Infrastructure\Elasticsearch\ElasticsearchClient; use Elasticsearch\Common\Exceptions\Missing404Exception; + use function Lambdish\Phunctional\get_in; use function Lambdish\Phunctional\map; abstract class ElasticsearchRepository { - private ElasticsearchClient $client; - - public function __construct(ElasticsearchClient $client) - { - $this->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 b01b0463d..b32a8be34 100644 --- a/src/Shared/Infrastructure/PhpRandomNumberGenerator.php +++ b/src/Shared/Infrastructure/PhpRandomNumberGenerator.php @@ -8,8 +8,8 @@ final class PhpRandomNumberGenerator implements RandomNumberGenerator { - public function generate(): int - { - return random_int(1, 5); - } + public function generate(): int + { + return random_int(1, 5); + } } diff --git a/src/Shared/Infrastructure/RamseyUuidGenerator.php b/src/Shared/Infrastructure/RamseyUuidGenerator.php index 36e46a640..8a8b703de 100644 --- a/src/Shared/Infrastructure/RamseyUuidGenerator.php +++ b/src/Shared/Infrastructure/RamseyUuidGenerator.php @@ -9,8 +9,8 @@ final class RamseyUuidGenerator implements UuidGenerator { - public function generate(): string - { - return Uuid::uuid4()->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 4ab57a6cd..0930af5ee 100644 --- a/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php +++ b/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php @@ -11,30 +11,30 @@ final class AddJsonBodyToRequestListener { - public function onKernelRequest(RequestEvent $event): void - { - $request = $event->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 eda372f9a..09dd9be51 100644 --- a/src/Shared/Infrastructure/Symfony/ApiController.php +++ b/src/Shared/Infrastructure/Symfony/ApiController.php @@ -9,36 +9,31 @@ use CodelyTv\Shared\Domain\Bus\Query\Query; use CodelyTv\Shared\Domain\Bus\Query\QueryBus; use CodelyTv\Shared\Domain\Bus\Query\Response; + use function Lambdish\Phunctional\each; abstract class ApiController { - private QueryBus $queryBus; - private CommandBus $commandBus; - - public function __construct( - QueryBus $queryBus, - CommandBus $commandBus, - ApiExceptionsHttpStatusCodeMapping $exceptionHandler - ) { - $this->queryBus = $queryBus; - $this->commandBus = $commandBus; - - 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); - } + 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 4474cfb09..d913d8766 100644 --- a/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php +++ b/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php @@ -6,40 +6,43 @@ use CodelyTv\Shared\Domain\DomainError; use CodelyTv\Shared\Domain\Utils; +use ReflectionClass; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Throwable; -final class ApiExceptionListener +final readonly class ApiExceptionListener { - private ApiExceptionsHttpStatusCodeMapping $exceptionHandler; - - public function __construct(ApiExceptionsHttpStatusCodeMapping $exceptionHandler) - { - $this->exceptionHandler = $exceptionHandler; - } - - public function onException(ExceptionEvent $event): void - { - $exception = $event->getThrowable(); - - $event->setResponse( - new JsonResponse( - [ - 'code' => $this->exceptionCodeFor($exception), - 'message' => $exception->getMessage(), - ], - $this->exceptionHandler->statusCodeFor(get_class($exception)) - ) - ); - } - - private function exceptionCodeFor(Throwable $error): string - { - $domainErrorClass = DomainError::class; - - return $error instanceof $domainErrorClass - ? $error->errorCode() - : Utils::toSnakeCase(Utils::extractClassName($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 c3591a0f7..8b1ad8a11 100644 --- a/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php +++ b/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php @@ -7,28 +7,30 @@ use InvalidArgumentException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + use function Lambdish\Phunctional\get; final class ApiExceptionsHttpStatusCodeMapping { - 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($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 8223a3a2b..0f3ad3aa8 100644 --- a/src/Shared/Infrastructure/Symfony/BasicHttpAuthMiddleware.php +++ b/src/Shared/Infrastructure/Symfony/BasicHttpAuthMiddleware.php @@ -12,54 +12,51 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; -final class BasicHttpAuthMiddleware +final readonly class BasicHttpAuthMiddleware { - private CommandBus $bus; - - public function __construct(CommandBus $bus) - { - $this->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 5a45b43f7..61bc6a523 100644 --- a/src/Shared/Infrastructure/Symfony/FlashSession.php +++ b/src/Shared/Infrastructure/Symfony/FlashSession.php @@ -5,38 +5,38 @@ namespace CodelyTv\Shared\Infrastructure\Symfony; use CodelyTv\Shared\Domain\Utils; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\RequestStack; final class FlashSession { - private static array $flashes = []; - - public function __construct(SessionInterface $session) - { - self::$flashes = Utils::dot($session->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 3448c9e2a..741d764f0 100644 --- a/src/Shared/Infrastructure/Symfony/WebController.php +++ b/src/Shared/Infrastructure/Symfony/WebController.php @@ -8,75 +8,67 @@ use CodelyTv\Shared\Domain\Bus\Query\QueryBus; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; -use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; use Twig\Environment; abstract class WebController extends ApiController { - private Environment $twig; - private RouterInterface $router; - private SessionInterface $session; + public function __construct( + private readonly Environment $twig, + private readonly RouterInterface $router, + private readonly RequestStack $requestStack, + QueryBus $queryBus, + CommandBus $commandBus, + ApiExceptionsHttpStatusCodeMapping $exceptionHandler + ) { + parent::__construct($queryBus, $commandBus, $exceptionHandler); + } - public function __construct( - Environment $twig, - RouterInterface $router, - SessionInterface $session, - QueryBus $queryBus, - CommandBus $commandBus, - ApiExceptionsHttpStatusCodeMapping $exceptionHandler - ) { - parent::__construct($queryBus, $commandBus, $exceptionHandler); + final public function render(string $templatePath, array $arguments = []): SymfonyResponse + { + return new SymfonyResponse($this->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 index 6e002f524..25787a7db 100644 --- a/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php +++ b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandlerTest.php @@ -14,49 +14,49 @@ final class AuthenticateUserCommandHandlerTest extends AuthModuleUnitTestCase { - private $handler; + private AuthenticateUserCommandHandler | null $handler; - protected function setUp(): void - { - parent::setUp(); + protected function setUp(): void + { + parent::setUp(); - $this->handler = new AuthenticateUserCommandHandler(new UserAuthenticator($this->repository())); - } + $this->handler = new AuthenticateUserCommandHandler(new UserAuthenticator($this->repository())); + } - /** @test */ - public function it_should_authenticate_a_valid_user(): void - { - $command = AuthenticateUserCommandMother::random(); - $authUser = AuthUserMother::fromCommand($command); + /** @test */ + public function it_should_authenticate_a_valid_user(): void + { + $command = AuthenticateUserCommandMother::create(); + $authUser = AuthUserMother::fromCommand($command); - $this->shouldSearch($authUser->username(), $authUser); + $this->shouldSearch($authUser->username(), $authUser); - $this->dispatch($command, $this->handler); - } + $this->dispatch($command, $this->handler); + } - /** @test */ - public function it_should_throw_an_exception_when_the_user_does_not_exist(): void - { - $this->expectException(InvalidAuthUsername::class); + /** @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()); + $command = AuthenticateUserCommandMother::create(); + $username = AuthUsernameMother::create($command->username()); - $this->shouldSearch($username); + $this->shouldSearch($username); - $this->dispatch($command, $this->handler); - } + $this->dispatch($command, $this->handler); + } - /** @test */ - public function it_should_throw_an_exception_when_the_password_does_not_math(): void - { - $this->expectException(InvalidAuthCredentials::class); + /** @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())); + $command = AuthenticateUserCommandMother::create(); + $authUser = AuthUserMother::create(username: AuthUsernameMother::create($command->username())); - $this->shouldSearch($authUser->username(), $authUser); + $this->shouldSearch($authUser->username(), $authUser); - $this->dispatch($command, $this->handler); - } + $this->dispatch($command, $this->handler); + } } diff --git a/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php index c58631ea2..4418e2b9f 100644 --- a/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php +++ b/tests/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php @@ -12,13 +12,13 @@ final class AuthenticateUserCommandMother { - public static function create(AuthUsername $username, AuthPassword $password): AuthenticateUserCommand - { - return new AuthenticateUserCommand($username->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 index eb8b5d207..4a7ad4a7c 100644 --- a/tests/Backoffice/Auth/AuthModuleUnitTestCase.php +++ b/tests/Backoffice/Auth/AuthModuleUnitTestCase.php @@ -12,20 +12,19 @@ abstract class AuthModuleUnitTestCase extends UnitTestCase { - private $repository; + private AuthRepository | MockInterface | null $repository = null; - protected function shouldSearch(AuthUsername $username, AuthUser $authUser = null): void - { - $this->repository() - ->shouldReceive('search') - ->with($this->similarTo($username)) - ->once() - ->andReturn($authUser); - } + protected function shouldSearch(AuthUsername $username, AuthUser $authUser = null): void + { + $this->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); - } + 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 index d88d293d0..9426c01c6 100644 --- a/tests/Backoffice/Auth/Domain/AuthPasswordMother.php +++ b/tests/Backoffice/Auth/Domain/AuthPasswordMother.php @@ -9,13 +9,8 @@ final class AuthPasswordMother { - public static function create(string $value): AuthPassword - { - return new AuthPassword($value); - } - - public static function random(): AuthPassword - { - return self::create(UuidMother::random()); - } + public static function create(?string $value = null): AuthPassword + { + return new AuthPassword($value ?? UuidMother::create()); + } } diff --git a/tests/Backoffice/Auth/Domain/AuthUserMother.php b/tests/Backoffice/Auth/Domain/AuthUserMother.php index 97f1847ca..76e3d34dc 100644 --- a/tests/Backoffice/Auth/Domain/AuthUserMother.php +++ b/tests/Backoffice/Auth/Domain/AuthUserMother.php @@ -11,26 +11,16 @@ final class AuthUserMother { - public static function create(AuthUsername $username, AuthPassword $password): AuthUser - { - return new AuthUser($username, $password); - } + public static function create(?AuthUsername $username = null, ?AuthPassword $password = null): AuthUser + { + return new AuthUser($username ?? AuthUsernameMother::create(), $password ?? AuthPasswordMother::create()); + } - public static function fromCommand(AuthenticateUserCommand $command): AuthUser - { - return self::create( - AuthUsernameMother::create($command->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()); - } + public static function fromCommand(AuthenticateUserCommand $command): AuthUser + { + return self::create( + AuthUsernameMother::create($command->username()), + AuthPasswordMother::create($command->password()) + ); + } } diff --git a/tests/Backoffice/Auth/Domain/AuthUsernameMother.php b/tests/Backoffice/Auth/Domain/AuthUsernameMother.php index fc87619b0..988cedff5 100644 --- a/tests/Backoffice/Auth/Domain/AuthUsernameMother.php +++ b/tests/Backoffice/Auth/Domain/AuthUsernameMother.php @@ -9,13 +9,8 @@ final class AuthUsernameMother { - public static function create(string $value): AuthUsername - { - return new AuthUsername($value); - } - - public static function random(): AuthUsername - { - return self::create(WordMother::random()); - } + public static function create(?string $value = null): AuthUsername + { + return new AuthUsername($value ?? WordMother::create()); + } } diff --git a/tests/Backoffice/Courses/BackofficeCoursesModuleInfrastructureTestCase.php b/tests/Backoffice/Courses/BackofficeCoursesModuleInfrastructureTestCase.php index 5898968d8..fe07bd4ae 100644 --- a/tests/Backoffice/Courses/BackofficeCoursesModuleInfrastructureTestCase.php +++ b/tests/Backoffice/Courses/BackofficeCoursesModuleInfrastructureTestCase.php @@ -4,14 +4,20 @@ namespace CodelyTv\Tests\Backoffice\Courses; +use CodelyTv\Backoffice\Courses\Infrastructure\Persistence\ElasticsearchBackofficeCourseRepository; use CodelyTv\Backoffice\Courses\Infrastructure\Persistence\MySqlBackofficeCourseRepository; -use CodelyTv\Tests\Mooc\Shared\Infrastructure\PhpUnit\MoocContextInfrastructureTestCase; +use CodelyTv\Tests\Backoffice\Shared\Infraestructure\PhpUnit\BackofficeContextInfrastructureTestCase; use Doctrine\ORM\EntityManager; -abstract class BackofficeCoursesModuleInfrastructureTestCase extends MoocContextInfrastructureTestCase +abstract class BackofficeCoursesModuleInfrastructureTestCase extends BackofficeContextInfrastructureTestCase { - protected function repository(): MySqlBackofficeCourseRepository - { - return new MySqlBackofficeCourseRepository($this->service(EntityManager::class)); - } + protected function mySqlRepository(): MySqlBackofficeCourseRepository + { + return new MySqlBackofficeCourseRepository($this->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 index 85168553d..58e38d029 100644 --- a/tests/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php +++ b/tests/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php @@ -11,18 +11,16 @@ final class BackofficeCourseCriteriaMother { - public static function nameContains(string $text): Criteria - { - return CriteriaMother::create( - FiltersMother::createOne( - FilterMother::fromValues( - [ - 'field' => 'name', - 'operator' => 'CONTAINS', - 'value' => $text, - ] - ) - ) - ); - } + public static function nameContains(string $text): Criteria + { + return CriteriaMother::create( + FiltersMother::createOne( + FilterMother::fromValues([ + 'field' => 'name', + 'operator' => 'CONTAINS', + 'value' => $text, + ]) + ) + ); + } } diff --git a/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php b/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php index 38c73b74e..9d2ffbe35 100644 --- a/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php +++ b/tests/Backoffice/Courses/Domain/BackofficeCourseMother.php @@ -11,26 +11,12 @@ final class BackofficeCourseMother { - public static function create(string $id, string $name, string $duration): BackofficeCourse - { - return new BackofficeCourse($id, $name, $duration); - } - - public static function withName(string $name): BackofficeCourse - { - return self::create( - CourseIdMother::random()->value(), - $name, - CourseDurationMother::random()->value() - ); - } - - public static function random(): BackofficeCourse - { - return self::create( - CourseIdMother::random()->value(), - CourseNameMother::random()->value(), - CourseDurationMother::random()->value() - ); - } + public static function create(?string $id = null, ?string $name = null, ?string $duration = null): BackofficeCourse + { + return new BackofficeCourse( + $id ?? CourseIdMother::create()->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 index 4d9d7b568..300d01d99 100644 --- a/tests/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php +++ b/tests/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepositoryTest.php @@ -11,54 +11,54 @@ final class MySqlBackofficeCourseRepositoryTest extends BackofficeCoursesModuleInfrastructureTestCase { - /** @test */ - public function it_should_save_a_valid_course(): void - { - $this->repository()->save(BackofficeCourseMother::random()); - } + /** @test */ + public function it_should_save_a_valid_course(): void + { + $this->mySqlRepository()->save(BackofficeCourseMother::create()); + } - /** @test */ - public function it_should_search_all_existing_courses(): void - { - $existingCourse = BackofficeCourseMother::random(); - $anotherExistingCourse = BackofficeCourseMother::random(); - $existingCourses = [$existingCourse, $anotherExistingCourse]; + /** @test */ + public function it_should_search_all_existing_courses(): void + { + $existingCourse = BackofficeCourseMother::create(); + $anotherExistingCourse = BackofficeCourseMother::create(); + $existingCourses = [$existingCourse, $anotherExistingCourse]; - $this->repository()->save($existingCourse); - $this->repository()->save($anotherExistingCourse); + $this->mySqlRepository()->save($existingCourse); + $this->mySqlRepository()->save($anotherExistingCourse); - $this->assertSimilar($existingCourses, $this->repository()->searchAll()); - } + $this->assertSimilar($existingCourses, $this->mySqlRepository()->searchAll()); + } - /** @test */ - public function it_should_search_all_existing_courses_with_an_empty_criteria(): void - { - $existingCourse = BackofficeCourseMother::random(); - $anotherExistingCourse = BackofficeCourseMother::random(); - $existingCourses = [$existingCourse, $anotherExistingCourse]; + /** @test */ + public function it_should_search_all_existing_courses_with_an_empty_criteria(): void + { + $existingCourse = BackofficeCourseMother::create(); + $anotherExistingCourse = BackofficeCourseMother::create(); + $existingCourses = [$existingCourse, $anotherExistingCourse]; - $this->repository()->save($existingCourse); - $this->repository()->save($anotherExistingCourse); - $this->clearUnitOfWork(); + $this->mySqlRepository()->save($existingCourse); + $this->mySqlRepository()->save($anotherExistingCourse); + $this->clearUnitOfWork(); - $this->assertSimilar($existingCourses, $this->repository()->matching(CriteriaMother::empty())); - } + $this->assertSimilar($existingCourses, $this->mySqlRepository()->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]; + /** @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'); + $nameContainsDddCriteria = BackofficeCourseCriteriaMother::nameContains('DDD'); - $this->repository()->save($dddInJavaCourse); - $this->repository()->save($dddInPhpCourse); - $this->repository()->save($intellijCourse); - $this->clearUnitOfWork(); + $this->mySqlRepository()->save($dddInJavaCourse); + $this->mySqlRepository()->save($dddInPhpCourse); + $this->mySqlRepository()->save($intellijCourse); + $this->clearUnitOfWork(); - $this->assertSimilar($dddCourses, $this->repository()->matching($nameContainsDddCriteria)); - } + $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 index 46267619c..4fe82e139 100644 --- a/tests/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php +++ b/tests/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php @@ -12,26 +12,26 @@ final class CreateCourseCommandHandlerTest extends CoursesModuleUnitTestCase { - private $handler; + private CreateCourseCommandHandler | null $handler; - protected function setUp(): void - { - parent::setUp(); + protected function setUp(): void + { + parent::setUp(); - $this->handler = new CreateCourseCommandHandler(new CourseCreator($this->repository(), $this->eventBus())); - } + $this->handler = new CreateCourseCommandHandler(new CourseCreator($this->repository(), $this->eventBus())); + } - /** @test */ - public function it_should_create_a_valid_course(): void - { - $command = CreateCourseCommandMother::random(); + /** @test */ + public function it_should_create_a_valid_course(): void + { + $command = CreateCourseCommandMother::create(); - $course = CourseMother::fromRequest($command); - $domainEvent = CourseCreatedDomainEventMother::fromCourse($course); + $course = CourseMother::fromRequest($command); + $domainEvent = CourseCreatedDomainEventMother::fromCourse($course); - $this->shouldSave($course); - $this->shouldPublishDomainEvent($domainEvent); + $this->shouldSave($course); + $this->shouldPublishDomainEvent($domainEvent); - $this->dispatch($command, $this->handler); - } + $this->dispatch($command, $this->handler); + } } diff --git a/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php b/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php index dba54ff00..9d4b31fea 100644 --- a/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php +++ b/tests/Mooc/Courses/Application/Create/CreateCourseCommandMother.php @@ -7,20 +7,22 @@ use CodelyTv\Mooc\Courses\Application\Create\CreateCourseCommand; use CodelyTv\Mooc\Courses\Domain\CourseDuration; use CodelyTv\Mooc\Courses\Domain\CourseName; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Tests\Mooc\Courses\Domain\CourseDurationMother; use CodelyTv\Tests\Mooc\Courses\Domain\CourseIdMother; use CodelyTv\Tests\Mooc\Courses\Domain\CourseNameMother; final class CreateCourseCommandMother { - public static function create(CourseId $id, CourseName $name, CourseDuration $duration): CreateCourseCommand - { - return new CreateCourseCommand($id->value(), $name->value(), $duration->value()); - } - - public static function random(): CreateCourseCommand - { - return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random()); - } + public static function create( + ?CourseId $id = null, + ?CourseName $name = null, + ?CourseDuration $duration = null + ): CreateCourseCommand { + return new CreateCourseCommand( + $id?->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 index 2edcf64f1..7890b36f7 100644 --- a/tests/Mooc/Courses/Application/Update/CourseRenamerTest.php +++ b/tests/Mooc/Courses/Application/Update/CourseRenamerTest.php @@ -14,38 +14,38 @@ final class CourseRenamerTest extends CoursesModuleUnitTestCase { - private $renamer; + private CourseRenamer | null $renamer; - protected function setUp(): void - { - parent::setUp(); + protected function setUp(): void + { + parent::setUp(); - $this->renamer = new CourseRenamer($this->repository(), $this->eventBus()); - } + $this->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]); + /** @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->shouldSearch($course->id(), $course); + $this->shouldSave($renamedCourse); + $this->shouldNotPublishDomainEvent(); - $this->renamer->__invoke($course->id(), $newName); - } + $this->renamer->__invoke($course->id(), $newName); + } - /** @test */ - public function it_should_throw_an_exception_when_the_course_not_exist(): void - { - $this->expectException(CourseNotExist::class); + /** @test */ + public function it_should_throw_an_exception_when_the_course_not_exist(): void + { + $this->expectException(CourseNotExist::class); - $id = CourseIdMother::random(); + $id = CourseIdMother::create(); - $this->shouldSearch($id, null); + $this->shouldSearch($id, null); - $this->renamer->__invoke($id, CourseNameMother::random()); - } + $this->renamer->__invoke($id, CourseNameMother::create()); + } } diff --git a/tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php b/tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php index b81e7d9e8..67ad24223 100644 --- a/tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php +++ b/tests/Mooc/Courses/CoursesModuleInfrastructureTestCase.php @@ -9,8 +9,8 @@ abstract class CoursesModuleInfrastructureTestCase extends MoocContextInfrastructureTestCase { - protected function repository(): CourseRepository - { - return $this->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 index 174662f33..b4d269546 100644 --- a/tests/Mooc/Courses/CoursesModuleUnitTestCase.php +++ b/tests/Mooc/Courses/CoursesModuleUnitTestCase.php @@ -6,35 +6,34 @@ use CodelyTv\Mooc\Courses\Domain\Course; use CodelyTv\Mooc\Courses\Domain\CourseRepository; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Tests\Shared\Infrastructure\PhpUnit\UnitTestCase; use Mockery\MockInterface; abstract class CoursesModuleUnitTestCase extends UnitTestCase { - private $repository; + private CourseRepository | MockInterface | null $repository = null; - protected function shouldSave(Course $course): void - { - $this->repository() - ->shouldReceive('save') - ->with($this->similarTo($course)) - ->once() - ->andReturnNull(); - } + protected function shouldSave(Course $course): void + { + $this->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 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); - } + 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 index 1081244d3..b04f7109d 100644 --- a/tests/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php +++ b/tests/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php @@ -8,22 +8,24 @@ use CodelyTv\Mooc\Courses\Domain\CourseCreatedDomainEvent; use CodelyTv\Mooc\Courses\Domain\CourseDuration; use CodelyTv\Mooc\Courses\Domain\CourseName; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; final class CourseCreatedDomainEventMother { - public static function create(CourseId $id, CourseName $name, CourseDuration $duration): CourseCreatedDomainEvent - { - return new CourseCreatedDomainEvent($id->value(), $name->value(), $duration->value()); - } + public static function create( + ?CourseId $id = null, + ?CourseName $name = null, + ?CourseDuration $duration = null + ): CourseCreatedDomainEvent { + return new CourseCreatedDomainEvent( + $id?->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()); - } - - public static function random(): CourseCreatedDomainEvent - { - return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random()); - } + 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 index 14c279a7c..2cfc68c06 100644 --- a/tests/Mooc/Courses/Domain/CourseDurationMother.php +++ b/tests/Mooc/Courses/Domain/CourseDurationMother.php @@ -10,19 +10,17 @@ final class CourseDurationMother { - public static function create(string $value): CourseDuration - { - return new CourseDuration($value); - } + public static function create(?string $value = null): CourseDuration + { + return new CourseDuration($value ?? self::random()); + } - public static function random(): CourseDuration - { - return self::create( - sprintf( - '%s %s', - IntegerMother::lessThan(100), - RandomElementPicker::from('months', 'years', 'days', 'hours', 'minutes', 'seconds') - ) - ); - } + private static function random(): string + { + return sprintf( + '%s %s', + IntegerMother::lessThan(100), + RandomElementPicker::from('months', 'years', 'days', 'hours', 'minutes', 'seconds') + ); + } } diff --git a/tests/Mooc/Courses/Domain/CourseIdMother.php b/tests/Mooc/Courses/Domain/CourseIdMother.php index e942d0179..2b180910f 100644 --- a/tests/Mooc/Courses/Domain/CourseIdMother.php +++ b/tests/Mooc/Courses/Domain/CourseIdMother.php @@ -4,23 +4,13 @@ namespace CodelyTv\Tests\Mooc\Courses\Domain; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Tests\Shared\Domain\UuidMother; final class CourseIdMother { - public static function create(string $value): CourseId - { - return new CourseId($value); - } - - public static function creator(): callable - { - return static fn() => self::random(); - } - - public static function random(): CourseId - { - return self::create(UuidMother::random()); - } + public static function create(?string $value = null): CourseId + { + return new CourseId($value ?? UuidMother::create()); + } } diff --git a/tests/Mooc/Courses/Domain/CourseMother.php b/tests/Mooc/Courses/Domain/CourseMother.php index 707344c23..019770869 100644 --- a/tests/Mooc/Courses/Domain/CourseMother.php +++ b/tests/Mooc/Courses/Domain/CourseMother.php @@ -8,26 +8,28 @@ use CodelyTv\Mooc\Courses\Domain\Course; use CodelyTv\Mooc\Courses\Domain\CourseDuration; use CodelyTv\Mooc\Courses\Domain\CourseName; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; final class CourseMother { - public static function create(CourseId $id, CourseName $name, CourseDuration $duration): Course - { - return new Course($id, $name, $duration); - } + public static function create( + ?CourseId $id = null, + ?CourseName $name = null, + ?CourseDuration $duration = null + ): Course { + return new Course( + $id ?? CourseIdMother::create(), + $name ?? CourseNameMother::create(), + $duration ?? CourseDurationMother::create() + ); + } - public static function fromRequest(CreateCourseCommand $request): Course - { - return self::create( - CourseIdMother::create($request->id()), - CourseNameMother::create($request->name()), - CourseDurationMother::create($request->duration()) - ); - } - - public static function random(): Course - { - return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random()); - } + public static function fromRequest(CreateCourseCommand $request): Course + { + return self::create( + CourseIdMother::create($request->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 index 6a01e95aa..72c4e7e02 100644 --- a/tests/Mooc/Courses/Domain/CourseNameMother.php +++ b/tests/Mooc/Courses/Domain/CourseNameMother.php @@ -9,13 +9,8 @@ final class CourseNameMother { - public static function create(string $value): CourseName - { - return new CourseName($value); - } - - public static function random(): CourseName - { - return self::create(WordMother::random()); - } + public static function create(?string $value = null): CourseName + { + return new CourseName($value ?? WordMother::create()); + } } diff --git a/tests/Mooc/Courses/Infrastructure/Persistence/CourseRepositoryTest.php b/tests/Mooc/Courses/Infrastructure/Persistence/CourseRepositoryTest.php index f15356a87..56f84522c 100644 --- a/tests/Mooc/Courses/Infrastructure/Persistence/CourseRepositoryTest.php +++ b/tests/Mooc/Courses/Infrastructure/Persistence/CourseRepositoryTest.php @@ -10,27 +10,27 @@ final class CourseRepositoryTest extends CoursesModuleInfrastructureTestCase { - /** @test */ - public function it_should_save_a_course(): void - { - $course = CourseMother::random(); + /** @test */ + public function it_should_save_a_course(): void + { + $course = CourseMother::create(); - $this->repository()->save($course); - } + $this->repository()->save($course); + } - /** @test */ - public function it_should_return_an_existing_course(): void - { - $course = CourseMother::random(); + /** @test */ + public function it_should_return_an_existing_course(): void + { + $course = CourseMother::create(); - $this->repository()->save($course); + $this->repository()->save($course); - $this->assertEquals($course, $this->repository()->search($course->id())); - } + $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())); - } + /** @test */ + public function it_should_not_return_a_non_existing_course(): void + { + $this->assertNull($this->repository()->search(CourseIdMother::create())); + } } diff --git a/tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php b/tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php index 4b3996d7c..07a588148 100644 --- a/tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php +++ b/tests/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php @@ -10,13 +10,8 @@ final class CoursesCounterResponseMother { - public static function create(CoursesCounterTotal $total): CoursesCounterResponse - { - return new CoursesCounterResponse($total->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 index 5da009538..2c8bc2ef5 100644 --- a/tests/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php +++ b/tests/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQueryHandlerTest.php @@ -13,34 +13,34 @@ final class FindCoursesCounterQueryHandlerTest extends CoursesCounterModuleUnitTestCase { - private $handler; + private FindCoursesCounterQueryHandler | null $handler; - protected function setUp(): void - { - parent::setUp(); + protected function setUp(): void + { + parent::setUp(); - $this->handler = new FindCoursesCounterQueryHandler(new CoursesCounterFinder($this->repository())); - } + $this->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()); + /** @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->shouldSearch($counter); - $this->assertAskResponse($response, $query, $this->handler); - } + $this->assertAskResponse($response, $query, $this->handler); + } - /** @test */ - public function it_should_throw_an_exception_when_courses_counter_does_not_exists(): void - { - $query = new FindCoursesCounterQuery(); + /** @test */ + public function it_should_throw_an_exception_when_courses_counter_does_not_exists(): void + { + $query = new FindCoursesCounterQuery(); - $this->shouldSearch(null); + $this->shouldSearch(null); - $this->assertAskThrowsException(CoursesCounterNotExist::class, $query, $this->handler); - } + $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 index cf577b8e4..b81642fc2 100644 --- a/tests/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php +++ b/tests/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreatedTest.php @@ -14,65 +14,61 @@ final class IncrementCoursesCounterOnCourseCreatedTest extends CoursesCounterModuleUnitTestCase { - private $subscriber; - - protected function setUp(): void - { - parent::setUp(); - - $this->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); - } + private IncrementCoursesCounterOnCourseCreated | null $subscriber; + + protected function setUp(): void + { + parent::setUp(); + + $this->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 index 4ca746727..6937e3902 100644 --- a/tests/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php +++ b/tests/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php @@ -11,28 +11,27 @@ abstract class CoursesCounterModuleUnitTestCase extends UnitTestCase { - private $repository; + private CoursesCounterRepository | MockInterface | null $repository = null; - protected function shouldSave(CoursesCounter $course): void - { - $this->repository() - ->shouldReceive('save') - ->once() - ->with($this->similarTo($course)) - ->andReturnNull(); - } + protected function shouldSave(CoursesCounter $course): void + { + $this->repository() + ->shouldReceive('save') + ->once() + ->with($this->similarTo($course)) + ->andReturnNull(); + } - protected function shouldSearch(?CoursesCounter $counter): void - { - $this->repository() - ->shouldReceive('search') - ->once() - ->andReturn($counter); - } + 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); - } + 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 index ce3089cc6..98bf659a1 100644 --- a/tests/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php @@ -9,13 +9,8 @@ final class CoursesCounterIdMother { - public static function create(string $value): CoursesCounterId - { - return new CoursesCounterId($value); - } - - public static function random(): CoursesCounterId - { - return self::create(UuidMother::random()); - } + public static function create(?string $value = null): CoursesCounterId + { + return new CoursesCounterId($value ?? UuidMother::create()); + } } diff --git a/tests/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEventMother.php b/tests/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEventMother.php index 8a3c60bba..a3dc0b881 100644 --- a/tests/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEventMother.php +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterIncrementedDomainEventMother.php @@ -11,20 +11,18 @@ final class CoursesCounterIncrementedDomainEventMother { - public static function create( - CoursesCounterId $id, - CoursesCounterTotal $total - ): CoursesCounterIncrementedDomainEvent { - return new CoursesCounterIncrementedDomainEvent($id->value(), $total->value()); - } + public static function create( + ?CoursesCounterId $id = null, + ?CoursesCounterTotal $total = null + ): CoursesCounterIncrementedDomainEvent { + return new CoursesCounterIncrementedDomainEvent( + $id?->value() ?? CoursesCounterIdMother::create()->value(), + $total?->value() ?? CoursesCounterTotalMother::create()->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()); - } + 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 index cf044c3b7..d5200a5cd 100644 --- a/tests/Mooc/CoursesCounter/Domain/CoursesCounterMother.php +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterMother.php @@ -7,40 +7,35 @@ use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounter; use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounterId; use CodelyTv\Mooc\CoursesCounter\Domain\CoursesCounterTotal; -use CodelyTv\Mooc\Shared\Domain\Course\CourseId; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Tests\Mooc\Courses\Domain\CourseIdMother; use CodelyTv\Tests\Shared\Domain\Repeater; final class CoursesCounterMother { - public static function create( - CoursesCounterId $id, - CoursesCounterTotal $total, - CourseId ...$existingCourses - ): CoursesCounter { - return new CoursesCounter($id, $total, ...$existingCourses); - } + public static function create( + ?CoursesCounterId $id = null, + ?CoursesCounterTotal $total = null, + CourseId ...$existingCourses + ): CoursesCounter { + return new CoursesCounter( + $id ?? CoursesCounterIdMother::create(), + $total ?? CoursesCounterTotalMother::create(), + ...count($existingCourses) ? $existingCourses : Repeater::random(fn (): CourseId => CourseIdMother::create()) + ); + } - public static function withOne(CourseId $courseId): CoursesCounter - { - return self::create(CoursesCounterIdMother::random(), CoursesCounterTotalMother::one(), $courseId); - } + 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]) - ); - } - - public static function random(): CoursesCounter - { - return self::create( - CoursesCounterIdMother::random(), - CoursesCounterTotalMother::random(), - ...Repeater::random(CourseIdMother::creator()) - ); - } + 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 index f151d5855..0c29d090a 100644 --- a/tests/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php +++ b/tests/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php @@ -9,18 +9,18 @@ final class CoursesCounterTotalMother { - public static function create(int $value): CoursesCounterTotal - { - return new CoursesCounterTotal($value); - } + public static function create(?int $value = null): CoursesCounterTotal + { + return new CoursesCounterTotal($value ?? IntegerMother::create()); + } - public static function one(): CoursesCounterTotal - { - return self::create(1); - } + public static function one(): CoursesCounterTotal + { + return self::create(1); + } - public static function random(): CoursesCounterTotal - { - return self::create(IntegerMother::random()); - } + public static function random(): CoursesCounterTotal + { + return self::create(IntegerMother::create()); + } } diff --git a/tests/Mooc/MoocArchitectureTest.php b/tests/Mooc/MoocArchitectureTest.php new file mode 100644 index 000000000..e1e57197f --- /dev/null +++ b/tests/Mooc/MoocArchitectureTest.php @@ -0,0 +1,56 @@ +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/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php index 031e21057..fb4bef7bf 100644 --- a/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php +++ b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocContextInfrastructureTestCase.php @@ -4,31 +4,32 @@ namespace CodelyTv\Tests\Mooc\Shared\Infrastructure\PhpUnit; +use CodelyTv\Apps\Mooc\Backend\MoocBackendKernel; use CodelyTv\Tests\Shared\Infrastructure\PhpUnit\InfrastructureTestCase; use Doctrine\ORM\EntityManager; abstract class MoocContextInfrastructureTestCase extends InfrastructureTestCase { - protected function setUp(): void - { - parent::setUp(); + protected function setUp(): void + { + parent::setUp(); - $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); + $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); - $arranger->arrange(); - } + $arranger->arrange(); + } - protected function tearDown(): void - { - $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); + protected function tearDown(): void + { + $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class)); - $arranger->close(); + $arranger->close(); - parent::tearDown(); - } + parent::tearDown(); + } - protected function clearUnitOfWork(): void - { - $this->service(EntityManager::class)->clear(); - } + 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 index 5a8eb7032..39ddf5ef5 100644 --- a/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php +++ b/tests/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php @@ -5,25 +5,19 @@ namespace CodelyTv\Tests\Mooc\Shared\Infrastructure\PhpUnit; use CodelyTv\Tests\Shared\Infrastructure\Arranger\EnvironmentArranger; -use CodelyTv\Tests\Shared\Infrastructure\Doctrine\DatabaseCleaner; +use CodelyTv\Tests\Shared\Infrastructure\Doctrine\MySqlDatabaseCleaner; use Doctrine\ORM\EntityManager; + use function Lambdish\Phunctional\apply; -final class MoocEnvironmentArranger implements EnvironmentArranger +final readonly class MoocEnvironmentArranger implements EnvironmentArranger { - private EntityManager $entityManager; - - public function __construct(EntityManager $entityManager) - { - $this->entityManager = $entityManager; - } + public function __construct(private EntityManager $entityManager) {} - public function arrange(): void - { - apply(new DatabaseCleaner(), [$this->entityManager]); - } + public function arrange(): void + { + apply(new MySqlDatabaseCleaner(), [$this->entityManager]); + } - public function close(): void - { - } + 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/Shared/Domain/Criteria/CriteriaMother.php b/tests/Shared/Domain/Criteria/CriteriaMother.php index 5c0f8c35f..b46d45089 100644 --- a/tests/Shared/Domain/Criteria/CriteriaMother.php +++ b/tests/Shared/Domain/Criteria/CriteriaMother.php @@ -7,31 +7,20 @@ use CodelyTv\Shared\Domain\Criteria\Criteria; use CodelyTv\Shared\Domain\Criteria\Filters; use CodelyTv\Shared\Domain\Criteria\Order; -use CodelyTv\Tests\Shared\Domain\IntegerMother; final class CriteriaMother { - public static function create( - Filters $filters, - Order $order = null, - int $offset = null, - int $limit = null - ): Criteria { - return new Criteria($filters, $order ?: OrderMother::none(), $offset, $limit); - } + public static function create( + Filters $filters, + Order $order = null, + int $offset = null, + int $limit = null + ): Criteria { + return new Criteria($filters, $order ?: OrderMother::none(), $offset, $limit); + } - public static function empty(): Criteria - { - return self::create(FiltersMother::blank(), OrderMother::none()); - } - - public static function random(): Criteria - { - return self::create( - FiltersMother::random(), - OrderMother::random(), - IntegerMother::random(), - IntegerMother::random() - ); - } + public static function empty(): Criteria + { + return self::create(FiltersMother::blank(), OrderMother::none()); + } } diff --git a/tests/Shared/Domain/Criteria/FilterFieldMother.php b/tests/Shared/Domain/Criteria/FilterFieldMother.php index dc5cbbdd1..fdf89a4ae 100644 --- a/tests/Shared/Domain/Criteria/FilterFieldMother.php +++ b/tests/Shared/Domain/Criteria/FilterFieldMother.php @@ -9,13 +9,8 @@ final class FilterFieldMother { - public static function create($fieldName): FilterField - { - return new FilterField($fieldName); - } - - public static function random(): FilterField - { - return self::create(WordMother::random()); - } + public static function create(?string $fieldName = null): FilterField + { + return new FilterField($fieldName ?? WordMother::create()); + } } diff --git a/tests/Shared/Domain/Criteria/FilterMother.php b/tests/Shared/Domain/Criteria/FilterMother.php index 80a6ecdbc..ece793fd6 100644 --- a/tests/Shared/Domain/Criteria/FilterMother.php +++ b/tests/Shared/Domain/Criteria/FilterMother.php @@ -8,21 +8,38 @@ use CodelyTv\Shared\Domain\Criteria\FilterField; use CodelyTv\Shared\Domain\Criteria\FilterOperator; use CodelyTv\Shared\Domain\Criteria\FilterValue; +use CodelyTv\Tests\Shared\Domain\RandomElementPicker; final class FilterMother { - public static function create(FilterField $field, FilterOperator $operator, FilterValue $value): Filter - { - return new Filter($field, $operator, $value); - } + public static function create( + ?FilterField $field = null, + ?FilterOperator $operator = null, + ?FilterValue $value = null + ): Filter { + return new Filter( + $field ?? FilterFieldMother::create(), + $operator ?? self::randomOperator(), + $value ?? FilterValueMother::create() + ); + } - public static function fromValues($values): Filter - { - return Filter::fromValues($values); - } + /** @param string[] $values */ + public static function fromValues(array $values): Filter + { + return Filter::fromValues($values); + } - public static function random(): Filter - { - return self::create(FilterFieldMother::random(), FilterOperator::random(), FilterValueMother::random()); - } + + private static function randomOperator(): FilterOperator + { + return RandomElementPicker::from( + FilterOperator::EQUAL, + FilterOperator::NOT_EQUAL, + FilterOperator::GT, + FilterOperator::LT, + FilterOperator::CONTAINS, + FilterOperator::NOT_CONTAINS + ); + } } diff --git a/tests/Shared/Domain/Criteria/FilterValueMother.php b/tests/Shared/Domain/Criteria/FilterValueMother.php index dd412835d..77c42c2e1 100644 --- a/tests/Shared/Domain/Criteria/FilterValueMother.php +++ b/tests/Shared/Domain/Criteria/FilterValueMother.php @@ -9,13 +9,8 @@ final class FilterValueMother { - public static function create($value): FilterValue - { - return new FilterValue($value); - } - - public static function random(): FilterValue - { - return self::create(WordMother::random()); - } + public static function create(?string $value = null): FilterValue + { + return new FilterValue($value ?? WordMother::create()); + } } diff --git a/tests/Shared/Domain/Criteria/FiltersMother.php b/tests/Shared/Domain/Criteria/FiltersMother.php index 8d4b6de4e..08678f850 100644 --- a/tests/Shared/Domain/Criteria/FiltersMother.php +++ b/tests/Shared/Domain/Criteria/FiltersMother.php @@ -9,24 +9,19 @@ final class FiltersMother { - /** @param Filter[] $filters */ - public static function create(array $filters): Filters - { - return new Filters($filters); - } + /** @param Filter[] $filters */ + public static function create(array $filters): Filters + { + return new Filters($filters); + } - public static function createOne(Filter $filter): Filters - { - return self::create([$filter]); - } + public static function createOne(Filter $filter): Filters + { + return self::create([$filter]); + } - public static function blank(): Filters - { - return self::create([]); - } - - public static function random(): Filters - { - return self::create([FilterMother::random()]); - } + public static function blank(): Filters + { + return self::create([]); + } } diff --git a/tests/Shared/Domain/Criteria/OrderByMother.php b/tests/Shared/Domain/Criteria/OrderByMother.php index 38b534c97..1a1cf2df3 100644 --- a/tests/Shared/Domain/Criteria/OrderByMother.php +++ b/tests/Shared/Domain/Criteria/OrderByMother.php @@ -9,13 +9,8 @@ final class OrderByMother { - public static function create($fieldName): OrderBy - { - return new OrderBy($fieldName); - } - - public static function random(): OrderBy - { - return self::create(WordMother::random()); - } + public static function create(?string $fieldName = null): OrderBy + { + return new OrderBy($fieldName ?? WordMother::create()); + } } diff --git a/tests/Shared/Domain/Criteria/OrderMother.php b/tests/Shared/Domain/Criteria/OrderMother.php index 08b2c72e6..e574061ea 100644 --- a/tests/Shared/Domain/Criteria/OrderMother.php +++ b/tests/Shared/Domain/Criteria/OrderMother.php @@ -7,26 +7,22 @@ use CodelyTv\Shared\Domain\Criteria\Order; use CodelyTv\Shared\Domain\Criteria\OrderBy; use CodelyTv\Shared\Domain\Criteria\OrderType; +use CodelyTv\Tests\Shared\Domain\RandomElementPicker; final class OrderMother { - public static function create(OrderBy $orderBy, OrderType $orderType): Order - { - return new Order($orderBy, $orderType); - } + public static function create(?OrderBy $orderBy = null, ?OrderType $orderType = null): Order + { + return new Order($orderBy ?? OrderByMother::create(), $orderType ?? self::randomOrderType()); + } - public static function createDesc(string $orderBy): Order - { - return Order::createDesc(OrderByMother::create($orderBy)); - } + public static function none(): Order + { + return Order::none(); + } - public static function none(): Order - { - return Order::none(); - } - - public static function random(): Order - { - return self::create(OrderByMother::random(), OrderType::random()); - } + private static function randomOrderType(): Order + { + return RandomElementPicker::from(OrderType::ASC, OrderType::DESC, OrderType::NONE); + } } diff --git a/tests/Shared/Domain/DuplicatorMother.php b/tests/Shared/Domain/DuplicatorMother.php index 93891ac0d..b22f40efa 100644 --- a/tests/Shared/Domain/DuplicatorMother.php +++ b/tests/Shared/Domain/DuplicatorMother.php @@ -6,25 +6,26 @@ use ReflectionObject; use ReflectionProperty; + use function Lambdish\Phunctional\each; final class DuplicatorMother { - public static function with($object, array $newParams) - { - $duplicated = clone $object; - $reflection = new ReflectionObject($duplicated); + public static function with(mixed $object, array $newParams): mixed + { + $duplicated = clone $object; + $reflection = new ReflectionObject($duplicated); - each( - static function (ReflectionProperty $property) use ($duplicated, $newParams) { - if (isset($newParams[$property->getName()])) { - $property->setAccessible(true); - $property->setValue($duplicated, $newParams[$property->getName()]); - } - }, - $reflection->getProperties() - ); + each( + static function (ReflectionProperty $property) use ($duplicated, $newParams): void { + if (isset($newParams[$property->getName()])) { + $property->setAccessible(true); + $property->setValue($duplicated, $newParams[$property->getName()]); + } + }, + $reflection->getProperties() + ); - return $duplicated; - } + return $duplicated; + } } diff --git a/tests/Shared/Domain/IntegerMother.php b/tests/Shared/Domain/IntegerMother.php index 8e88f4896..910a08cc8 100644 --- a/tests/Shared/Domain/IntegerMother.php +++ b/tests/Shared/Domain/IntegerMother.php @@ -6,23 +6,18 @@ final class IntegerMother { - public static function between($min, $max = PHP_INT_MAX): int - { - return MotherCreator::random()->numberBetween($min, $max); - } + public static function create(): int + { + return self::between(1); + } - public static function lessThan($max): int - { - return self::between(1, $max); - } + public static function between(int $min, int $max = PHP_INT_MAX): int + { + return MotherCreator::random()->numberBetween($min, $max); + } - public static function moreThan($min): int - { - return self::between($min); - } - - public static function random(): int - { - return self::between(1); - } + 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 index 26c4228fc..f2da13923 100644 --- a/tests/Shared/Domain/MotherCreator.php +++ b/tests/Shared/Domain/MotherCreator.php @@ -9,10 +9,10 @@ final class MotherCreator { - private static $faker; + private static ?Generator $faker = null; - public static function random(): Generator - { - return self::$faker = self::$faker ?: Factory::create(); - } + public static function random(): Generator + { + return self::$faker ??= Factory::create(); + } } diff --git a/tests/Shared/Domain/RandomElementPicker.php b/tests/Shared/Domain/RandomElementPicker.php index df1d1eccf..76e8b1eda 100644 --- a/tests/Shared/Domain/RandomElementPicker.php +++ b/tests/Shared/Domain/RandomElementPicker.php @@ -6,8 +6,8 @@ final class RandomElementPicker { - public static function from(...$elements) - { - return MotherCreator::random()->randomElement($elements); - } + public static function from(mixed ...$elements): mixed + { + return MotherCreator::random()->randomElement($elements); + } } diff --git a/tests/Shared/Domain/Repeater.php b/tests/Shared/Domain/Repeater.php index 7ad63e527..19781e14f 100644 --- a/tests/Shared/Domain/Repeater.php +++ b/tests/Shared/Domain/Repeater.php @@ -8,18 +8,13 @@ final class Repeater { - public static function repeat(callable $function, $quantity): array - { - return repeat($function, $quantity); - } + public static function repeat(callable $function, int $quantity): array + { + return repeat($function, $quantity); + } - public static function repeatLessThan(callable $function, $max): array - { - return self::repeat($function, IntegerMother::lessThan($max)); - } - - public static function random(callable $function): array - { - return self::repeat($function, IntegerMother::lessThan(5)); - } + public static function random(callable $function): array + { + return self::repeat($function, IntegerMother::lessThan(5)); + } } diff --git a/tests/Shared/Domain/TestUtils.php b/tests/Shared/Domain/TestUtils.php index cfb347deb..141bfea5e 100644 --- a/tests/Shared/Domain/TestUtils.php +++ b/tests/Shared/Domain/TestUtils.php @@ -9,22 +9,22 @@ final class TestUtils { - public static function isSimilar($expected, $actual): bool - { - $constraint = new CodelyTvConstraintIsSimilar($expected); + public static function isSimilar(mixed $expected, mixed $actual): bool + { + $constraint = new CodelyTvConstraintIsSimilar($expected); - return $constraint->evaluate($actual, '', true); - } + return $constraint->evaluate($actual, '', true); + } - public static function assertSimilar($expected, $actual): void - { - $constraint = new CodelyTvConstraintIsSimilar($expected); + public static function assertSimilar(mixed $expected, mixed $actual): void + { + $constraint = new CodelyTvConstraintIsSimilar($expected); - $constraint->evaluate($actual); - } + $constraint->evaluate($actual); + } - public static function similarTo($value, $delta = 0.0): CodelyTvMatcherIsSimilar - { - return new CodelyTvMatcherIsSimilar($value, $delta); - } + 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 index 3ec353506..20bd3516c 100644 --- a/tests/Shared/Domain/UuidMother.php +++ b/tests/Shared/Domain/UuidMother.php @@ -6,8 +6,8 @@ final class UuidMother { - public static function random(): string - { - return MotherCreator::random()->unique()->uuid; - } + public static function create(): string + { + return MotherCreator::random()->unique()->uuid; + } } diff --git a/tests/Shared/Domain/WordMother.php b/tests/Shared/Domain/WordMother.php index 5814eb85c..4bf988e35 100644 --- a/tests/Shared/Domain/WordMother.php +++ b/tests/Shared/Domain/WordMother.php @@ -6,8 +6,8 @@ final class WordMother { - public static function random(): string - { - return MotherCreator::random()->word; - } + public static function create(): string + { + return MotherCreator::random()->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 @@ +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)); - } + private readonly MinkHelper $sessionHelper; + private readonly MinkSessionRequestHelper $request; + + public function __construct(private readonly Session $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(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 index 0c9ec4083..29d6788a8 100644 --- a/tests/Shared/Infrastructure/Behat/ApplicationFeatureContext.php +++ b/tests/Shared/Infrastructure/Behat/ApplicationFeatureContext.php @@ -10,36 +10,28 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus; use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections; -final class ApplicationFeatureContext implements Context +final readonly class ApplicationFeatureContext implements Context { - private $connections; - private $bus; - private $deserializer; - - public function __construct( - DatabaseConnections $connections, - InMemorySymfonyEventBus $bus, - DomainEventJsonDeserializer $deserializer - ) { - $this->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); - } + public function __construct( + private DatabaseConnections $connections, + private InMemorySymfonyEventBus $bus, + private DomainEventJsonDeserializer $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): void + { + $domainEvent = $this->deserializer->deserialize($event->getRaw()); + + $this->bus->publish($domainEvent); + } } diff --git a/tests/Shared/Infrastructure/Bus/Command/FakeCommand.php b/tests/Shared/Infrastructure/Bus/Command/FakeCommand.php index bf13218ba..d009d2a79 100644 --- a/tests/Shared/Infrastructure/Bus/Command/FakeCommand.php +++ b/tests/Shared/Infrastructure/Bus/Command/FakeCommand.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\Bus\Command\Command; -final class FakeCommand implements Command -{ -} +final class FakeCommand implements Command {} diff --git a/tests/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php b/tests/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php index 80629cad8..b8dbff2b2 100644 --- a/tests/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php +++ b/tests/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBusTest.php @@ -8,42 +8,48 @@ use CodelyTv\Shared\Infrastructure\Bus\Command\CommandNotRegisteredError; use CodelyTv\Shared\Infrastructure\Bus\Command\InMemorySymfonyCommandBus; use CodelyTv\Tests\Shared\Infrastructure\PhpUnit\UnitTestCase; +use Mockery\MockInterface; use RuntimeException; final class InMemorySymfonyCommandBusTest extends UnitTestCase { - private $commandBus; - - protected function setUp(): void - { - parent::setUp(); - - $this->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!'); - } - }; - } + private InMemorySymfonyCommandBus | null $commandBus; + + protected function setUp(): void + { + parent::setUp(); + + $this->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 index fbaf5e2dd..ca17d6ceb 100644 --- a/tests/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php +++ b/tests/Shared/Infrastructure/Bus/Event/MySql/MySqlDoctrineEventBusTest.php @@ -4,6 +4,7 @@ namespace CodelyTv\Tests\Shared\Infrastructure\Bus\Event\MySql; +use CodelyTv\Apps\Mooc\Backend\MoocBackendKernel; use CodelyTv\Shared\Domain\Bus\Event\DomainEvent; use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventMapping; use CodelyTv\Shared\Infrastructure\Bus\Event\MySql\MySqlDoctrineDomainEventsConsumer; @@ -15,31 +16,36 @@ final class MySqlDoctrineEventBusTest extends InfrastructureTestCase { - private $bus; - private $consumer; - - protected function setUp(): void - { - parent::setUp(); - - $this->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( - fn(DomainEvent ...$expectedEvents) => $this->assertContainsEquals($domainEvent, $expectedEvents), - $eventsToConsume = 2 - ); - } + private MySqlDoctrineEventBus | null $bus; + private MySqlDoctrineDomainEventsConsumer | null $consumer; + + protected function setUp(): void + { + parent::setUp(); + + $this->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 index daa247034..b3d5b09e8 100644 --- a/tests/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php +++ b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusTest.php @@ -4,6 +4,7 @@ namespace CodelyTv\Tests\Shared\Infrastructure\Bus\Event\RabbitMq; +use CodelyTv\Apps\Mooc\Backend\MoocBackendKernel; use CodelyTv\Shared\Domain\Bus\Event\DomainEvent; use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventJsonDeserializer; use CodelyTv\Shared\Infrastructure\Bus\Event\MySql\MySqlDoctrineEventBus; @@ -20,159 +21,161 @@ final class RabbitMqEventBusTest extends InfrastructureTestCase { - private $connection; - private $exchangeName; - private $configurer; - private $publisher; - private $consumer; - private $fakeSubscriber; - private $consumerHasBeenExecuted; + private mixed $connection; + private string $exchangeName; + private RabbitMqConfigurer $configurer; + private RabbitMqEventBus $publisher; + private RabbitMqDomainEventsConsumer $consumer; + private TestAllWorksOnRabbitMqEventsPublished $fakeSubscriber; + private bool $consumerHasBeenExecuted; - protected function setUp(): void - { - parent::setUp(); + protected function setUp(): void + { + parent::setUp(); - $this->connection = $this->service(RabbitMqConnection::class); + $this->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->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); - } + $this->cleanEnvironment($this->connection); + } - /** @test */ - public function it_should_publish_and_consume_domain_events_from_rabbitmq(): void - { - $domainEvent = CourseCreatedDomainEventMother::random(); + /** @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->configurer->configure($this->exchangeName, $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->publisher->publish($domainEvent); - $this->assertDeadLetterContainsEvent(1); - } + $this->consumer->consume( + $this->assertConsumer($domainEvent), + RabbitMqQueueNameFormatter::format($this->fakeSubscriber) + ); - private function assertConsumer(DomainEvent ...$expectedDomainEvents): callable - { - return function (DomainEvent $domainEvent) use ($expectedDomainEvents): void { - $this->assertContainsEquals($domainEvent, $expectedDomainEvents); + $this->assertTrue($this->consumerHasBeenExecuted); + } - $this->consumerHasBeenExecuted = true; - }; - } + /** @test */ + public function it_should_throw_an_exception_consuming_non_existing_domain_events(): void + { + $this->expectException(RuntimeException::class); - private function failingConsumer(): callable - { - return static function (DomainEvent $domainEvent): void { - throw new RuntimeException('To test'); - }; - } + $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); - private function simulateErrorConsuming(): void - { - try { - $this->consumer->consume( - $this->failingConsumer(), - RabbitMqQueueNameFormatter::format($this->fakeSubscriber) - ); - } catch (Throwable $error) { - $this->assertInstanceOf(RuntimeException::class, $error); - } - } + $this->simulateErrorConsuming(); - 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(); - } + $this->assertDeadLetterContainsEvent(1); + } - private function assertDeadLetterContainsEvent(int $expectedNumberOfEvents): void - { - $totalEventsInDeadLetter = 0; + protected function kernelClass(): string + { + return MoocBackendKernel::class; + } - while ($this->connection->queue(RabbitMqQueueNameFormatter::formatDeadLetter($this->fakeSubscriber))->get( - AMQP_AUTOACK - )) { - $totalEventsInDeadLetter++; - } + private function assertConsumer(DomainEvent ...$expectedDomainEvents): callable + { + return function (DomainEvent $domainEvent) use ($expectedDomainEvents): void { + $this->assertContainsEquals($domainEvent, $expectedDomainEvents); - $this->assertSame($expectedNumberOfEvents, $totalEventsInDeadLetter); - } + $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/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php index 0287731a6..937f28e08 100644 --- a/tests/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php +++ b/tests/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php @@ -10,16 +10,10 @@ final class TestAllWorksOnRabbitMqEventsPublished implements DomainEventSubscriber { - public static function subscribedTo(): array - { - return [ - CourseCreatedDomainEvent::class, - CoursesCounterIncrementedDomainEvent::class, - ]; - } + public static function subscribedTo(): array + { + return [CourseCreatedDomainEvent::class, CoursesCounterIncrementedDomainEvent::class, ]; + } - /** @param CourseCreatedDomainEvent|CoursesCounterIncrementedDomainEvent $event */ - public function __invoke($event) - { - } + public function __invoke(CourseCreatedDomainEvent | CoursesCounterIncrementedDomainEvent $event): void {} } diff --git a/tests/Shared/Infrastructure/Bus/Query/FakeQuery.php b/tests/Shared/Infrastructure/Bus/Query/FakeQuery.php index 0afc89517..4f5f2ba62 100644 --- a/tests/Shared/Infrastructure/Bus/Query/FakeQuery.php +++ b/tests/Shared/Infrastructure/Bus/Query/FakeQuery.php @@ -6,6 +6,4 @@ use CodelyTv\Shared\Domain\Bus\Query\Query; -final class FakeQuery implements Query -{ -} +final class FakeQuery implements Query {} diff --git a/tests/Shared/Infrastructure/Bus/Query/FakeResponse.php b/tests/Shared/Infrastructure/Bus/Query/FakeResponse.php index 611e7bc99..41386dca5 100644 --- a/tests/Shared/Infrastructure/Bus/Query/FakeResponse.php +++ b/tests/Shared/Infrastructure/Bus/Query/FakeResponse.php @@ -6,17 +6,12 @@ use CodelyTv\Shared\Domain\Bus\Query\Response; -final class FakeResponse implements Response +final readonly class FakeResponse implements Response { - private int $number; + public function __construct(private int $number) {} - public function __construct(int $number) - { - $this->number = $number; - } - - public function number(): int - { - return $this->number; - } + public function number(): int + { + return $this->number; + } } diff --git a/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php b/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php index 8a1a06c90..dc3f2095b 100644 --- a/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php +++ b/tests/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php @@ -8,42 +8,48 @@ use CodelyTv\Shared\Infrastructure\Bus\Query\InMemorySymfonyQueryBus; use CodelyTv\Shared\Infrastructure\Bus\Query\QueryNotRegisteredError; use CodelyTv\Tests\Shared\Infrastructure\PhpUnit\UnitTestCase; +use Mockery\MockInterface; use RuntimeException; final class InMemorySymfonyQueryBusTest extends UnitTestCase { - private $queryBus; - - protected function setUp(): void - { - parent::setUp(); - - $this->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!'); - } - }; - } + private InMemorySymfonyQueryBus | null $queryBus; + + protected function setUp(): void + { + parent::setUp(); + + $this->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/Shared/Infrastructure/ConstantRandomNumberGenerator.php b/tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php index 2a5ef9eb7..810e216cc 100644 --- a/tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php +++ b/tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php @@ -8,8 +8,8 @@ final class ConstantRandomNumberGenerator implements RandomNumberGenerator { - public function generate(): int - { - return 1; - } + public function generate(): int + { + return 1; + } } diff --git a/tests/Shared/Infrastructure/Doctrine/DatabaseCleaner.php b/tests/Shared/Infrastructure/Doctrine/DatabaseCleaner.php deleted file mode 100644 index 47144db81..000000000 --- a/tests/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/Shared/Infrastructure/Doctrine/MySqlDatabaseCleaner.php b/tests/Shared/Infrastructure/Doctrine/MySqlDatabaseCleaner.php new file mode 100644 index 000000000..3c7cf7afc --- /dev/null +++ b/tests/Shared/Infrastructure/Doctrine/MySqlDatabaseCleaner.php @@ -0,0 +1,41 @@ +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 index cdc478b66..ea443d6e1 100644 --- a/tests/Shared/Infrastructure/Mink/MinkHelper.php +++ b/tests/Shared/Infrastructure/Mink/MinkHelper.php @@ -9,87 +9,72 @@ use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\DomCrawler\Crawler; -final class MinkHelper +final readonly class MinkHelper { - private Session $session; - - public function __construct(Session $session) - { - $this->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(); - } + public function __construct(private Session $session) {} + + public function sendRequest(string $method, string $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'] + ); + + 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 index af1cb25b9..df914dd98 100644 --- a/tests/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php +++ b/tests/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php @@ -7,28 +7,22 @@ use Behat\Gherkin\Node\PyStringNode; use Symfony\Component\DomCrawler\Crawler; -final class MinkSessionRequestHelper +final readonly class MinkSessionRequestHelper { - /** @var MinkHelper */ - private $sessionHelper; - - public function __construct($sessionHelper) - { - $this->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); - } + public function __construct(private MinkHelper $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(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 index 056fe1e26..1737b7adb 100644 --- a/tests/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php +++ b/tests/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php @@ -5,26 +5,25 @@ namespace CodelyTv\Tests\Shared\Infrastructure\Mockery; use CodelyTv\Tests\Shared\Infrastructure\PhpUnit\Constraint\CodelyTvConstraintIsSimilar; -use Mockery\Matcher\MatcherAbstract; +use Mockery\Matcher\MatcherInterface; +use Stringable; -final class CodelyTvMatcherIsSimilar extends MatcherAbstract +final readonly class CodelyTvMatcherIsSimilar implements Stringable, MatcherInterface { - private CodelyTvConstraintIsSimilar $constraint; - - public function __construct($value, $delta = 0.0) - { - parent::__construct($value); - - $this->constraint = new CodelyTvConstraintIsSimilar($value, $delta); - } - - public function match(&$actual): bool - { - return $this->constraint->evaluate($actual, '', true); - } - - public function __toString(): string - { - return 'Is similar'; - } + private CodelyTvConstraintIsSimilar $constraint; + + public function __construct(mixed $value, float $delta = 0.0) + { + $this->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 index babcc71a2..22ee1fccf 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootArraySimilarComparator.php @@ -8,41 +8,42 @@ use CodelyTv\Tests\Shared\Domain\TestUtils; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Comparator\ComparisonFailure; + use function Lambdish\Phunctional\all; use function Lambdish\Phunctional\any; use function Lambdish\Phunctional\instance_of; final class AggregateRootArraySimilarComparator extends Comparator { - public function accepts($expected, $actual): bool - { - return is_array($expected) && - is_array($actual) && - (all(instance_of(AggregateRoot::class), $expected) && - all(instance_of(AggregateRoot::class), $actual)); - } - - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void - { - if (!$this->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); - } + public function accepts($expected, $actual): bool + { + return is_array($expected) + && is_array($actual) + && (all(instance_of(AggregateRoot::class), $expected) + && all(instance_of(AggregateRoot::class), $actual)); + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void + { + if (!$this->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 index ab83e3045..aefd86a36 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/AggregateRootSimilarComparator.php @@ -12,67 +12,63 @@ final class AggregateRootSimilarComparator extends Comparator { - public function accepts($expected, $actual): bool - { - $aggregateRootClass = AggregateRoot::class; - - return $expected instanceof $aggregateRootClass && $actual instanceof $aggregateRootClass; - } - - /** - * @param AggregateRoot $expected - * @param AggregateRoot $actual - */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void - { - $actualEntity = clone $actual; - $actualEntity->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; - } + public function accepts($expected, $actual): bool + { + $aggregateRootClass = AggregateRoot::class; + + return $expected instanceof $aggregateRootClass && $actual instanceof $aggregateRootClass; + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void + { + $actualEntity = clone $actual; + $actualEntity->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 index a6ca2c917..67d3b5605 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeSimilarComparator.php @@ -12,39 +12,39 @@ final class DateTimeSimilarComparator extends ObjectComparator { - public function accepts($expected, $actual): bool - { - return $expected instanceof DateTimeInterface && $actual instanceof DateTimeInterface; - } - - public function assertEquals( - $expected, - $actual, - $delta = 0.0, - $canonicalize = false, - $ignoreCase = false, - array &$processed = [] - ): void { - $normalizedDelta = $delta === 0.0 ? 10 : $delta; - $intervalWithDelta = new DateInterval(sprintf('PT%sS', abs($normalizedDelta))); - - $expectedLower = clone $expected; - $expectedUpper = clone $expected; - - if ($actual < $expectedLower->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'; - } + public function accepts($expected, $actual): bool + { + return $expected instanceof DateTimeInterface && $actual instanceof DateTimeInterface; + } + + public function assertEquals( + $expected, + $actual, + $delta = 0.0, + $canonicalize = false, + $ignoreCase = false, + array &$processed = [] + ): void { + $normalizedDelta = $delta === 0.0 ? 10 : $delta; + $intervalWithDelta = new DateInterval(sprintf('PT%sS', abs($normalizedDelta))); + + $expectedLower = clone $expected; + $expectedUpper = clone $expected; + + if ($actual < $expectedLower->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 index 131362892..546c75cb9 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DateTimeStringSimilarComparator.php @@ -14,59 +14,58 @@ final class DateTimeStringSimilarComparator extends ObjectComparator { - public function accepts($expected, $actual): bool - { - return (null !== $actual) && - is_string($expected) && - is_string($actual) && - $this->isValidDateTimeString($expected) && - $this->isValidDateTimeString($actual); - } + public function accepts($expected, $actual): bool + { + return is_string($expected) + && is_string($actual) + && $this->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); + 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))); + $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.' - ); - } - } + 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); + protected function dateTimeToString(DateTimeInterface $datetime): string + { + $string = $datetime->format(DateTime::ATOM); - return $string ?: 'Invalid DateTime object'; - } + return $string ?: 'Invalid DateTime object'; + } - private function isValidDateTimeString($expected): bool - { - $isValid = true; + private function isValidDateTimeString(string $expected): bool + { + $isValid = true; - try { - new DateTimeImmutable($expected); - } catch (Throwable $throwable) { - $isValid = false; - } + try { + new DateTimeImmutable($expected); + } catch (Throwable) { + $isValid = false; + } - return $isValid; - } + return $isValid; + } } diff --git a/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php index 1802d24c0..c191f7fb0 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventArraySimilarComparator.php @@ -8,41 +8,42 @@ use CodelyTv\Tests\Shared\Domain\TestUtils; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Comparator\ComparisonFailure; + use function Lambdish\Phunctional\all; use function Lambdish\Phunctional\any; use function Lambdish\Phunctional\instance_of; final class DomainEventArraySimilarComparator extends Comparator { - public function accepts($expected, $actual): bool - { - return is_array($expected) && - is_array($actual) && - (all(instance_of(DomainEvent::class), $expected) && - all(instance_of(DomainEvent::class), $actual)); - } - - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void - { - if (!$this->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); - } + public function accepts($expected, $actual): bool + { + return is_array($expected) + && is_array($actual) + && (all(instance_of(DomainEvent::class), $expected) + && all(instance_of(DomainEvent::class), $actual)); + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void + { + if (!$this->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 index 72b724673..f14cb6a77 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php +++ b/tests/Shared/Infrastructure/PhpUnit/Comparator/DomainEventSimilarComparator.php @@ -12,68 +12,64 @@ final class DomainEventSimilarComparator extends Comparator { - private static $ignoredAttributes = ['eventId', 'occurredOn']; - - public function accepts($expected, $actual): bool - { - $domainEventRootClass = DomainEvent::class; - - return $expected instanceof $domainEventRootClass && $actual instanceof $domainEventRootClass; - } - - /** - * @param DomainEvent $expected - * @param DomainEvent $actual - */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void - { - if (!$this->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; - } + private static array $ignoredAttributes = ['eventId', 'occurredOn']; + + public function accepts($expected, $actual): bool + { + $domainEventRootClass = DomainEvent::class; + + return $expected instanceof $domainEventRootClass && $actual instanceof $domainEventRootClass; + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void + { + if (!$this->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 index b98f7eff2..e5b4ca53a 100644 --- a/tests/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php +++ b/tests/Shared/Infrastructure/PhpUnit/Constraint/CodelyTvConstraintIsSimilar.php @@ -14,82 +14,62 @@ use PHPUnit\Framework\ExpectationFailedException; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory; + use function is_string; use function sprintf; -use function strpos; // Based on \PHPUnit\Framework\Constraint\IsEqual final class CodelyTvConstraintIsSimilar extends Constraint { - private $value; - private $delta; - - public function __construct($value, float $delta = 0.0) - { - $this->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 - ); - } + public function __construct(private $value, private readonly float $delta = 0.0) {} + + 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 (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 index f459771f5..52438a8f0 100644 --- a/tests/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php +++ b/tests/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php @@ -4,39 +4,61 @@ namespace CodelyTv\Tests\Shared\Infrastructure\PhpUnit; -use CodelyTv\Tests\Mooc\Shared\Infrastructure\PhpUnit\MoocEnvironmentArranger; use CodelyTv\Tests\Shared\Domain\TestUtils; use Doctrine\ORM\EntityManager; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Throwable; abstract class InfrastructureTestCase extends KernelTestCase { - protected function setUp(): void - { - self::bootKernel(['environment' => '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); - } + abstract protected function kernelClass(): string; + + protected function setUp(): void + { + $_SERVER['KERNEL_CLASS'] = $this->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 index 3a0d8d901..599f2005d 100644 --- a/tests/Shared/Infrastructure/PhpUnit/UnitTestCase.php +++ b/tests/Shared/Infrastructure/PhpUnit/UnitTestCase.php @@ -11,94 +11,94 @@ use CodelyTv\Shared\Domain\Bus\Query\Response; use CodelyTv\Shared\Domain\UuidGenerator; use CodelyTv\Tests\Shared\Domain\TestUtils; +use CodelyTv\Tests\Shared\Infrastructure\Mockery\CodelyTvMatcherIsSimilar; use Mockery; use Mockery\Adapter\Phpunit\MockeryTestCase; -use Mockery\Matcher\MatcherAbstract; use Mockery\MockInterface; +use Throwable; abstract class UnitTestCase extends MockeryTestCase { - private $eventBus; - private $uuidGenerator; - - protected function mock(string $className): MockInterface - { - return Mockery::mock($className); - } - - protected function shouldPublishDomainEvent(DomainEvent $domainEvent): void - { - $this->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); - } + private EventBus | MockInterface | null $eventBus = null; + private MockInterface | UuidGenerator | null $uuidGenerator = null; + + protected function mock(string $className): MockInterface + { + return Mockery::mock($className); + } + + protected function shouldPublishDomainEvent(DomainEvent $domainEvent): void + { + $this->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(); + } +}