diff --git a/.docker/compose.yaml b/.docker/compose.yaml new file mode 100644 index 0000000..acab08c --- /dev/null +++ b/.docker/compose.yaml @@ -0,0 +1,14 @@ +x-build-args: &build-args + UID: "${UID:-1000}" + GID: "${GID:-1000}" + +name: cleverage-doctrine-process-bundle + +services: + php: + build: + context: php + args: + <<: *build-args + volumes: + - ../:/var/www diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile new file mode 100644 index 0000000..f98c3ba --- /dev/null +++ b/.docker/php/Dockerfile @@ -0,0 +1,29 @@ +FROM php:8.2-fpm-alpine + +ARG UID +ARG GID + +RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" +COPY /conf.d/ "$PHP_INI_DIR/conf.d/" + +RUN apk update && apk add \ + tzdata \ + shadow \ + nano \ + bash \ + icu-dev \ + && docker-php-ext-configure intl \ + && docker-php-ext-install intl opcache \ + && docker-php-ext-enable opcache + +RUN ln -s /usr/share/zoneinfo/Europe/Paris /etc/localtime \ + && sed -i "s/^;date.timezone =.*/date.timezone = Europe\/Paris/" $PHP_INI_DIR/php.ini + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +RUN usermod -u $UID www-data \ + && groupmod -g $GID www-data + +USER www-data:www-data + +WORKDIR /var/www diff --git a/.docker/php/conf.d/dev.ini b/.docker/php/conf.d/dev.ini new file mode 100644 index 0000000..2a141be --- /dev/null +++ b/.docker/php/conf.d/dev.ini @@ -0,0 +1,5 @@ +display_errors = 1 +error_reporting = E_ALL + +opcache.validate_timestamps = 1 +opcache.revalidate_freq = 0 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..7711713 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,14 @@ +## Description + + + +## Requirements + +* Documentation updates + - [ ] Reference + - [ ] Changelog +* [ ] Unit tests + +## Breaking changes + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..58db37d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## Description + + + +## Requirements + +* Documentation updates + - [ ] Reference + - [ ] Changelog +* [ ] Unit tests + +## Breaking changes + + diff --git a/.github/workflows/notifications.yml b/.github/workflows/notifications.yml new file mode 100644 index 0000000..e254116 --- /dev/null +++ b/.github/workflows/notifications.yml @@ -0,0 +1,23 @@ +name: Rocket chat notifications + +# Controls when the action will run. +on: + push: + tags: + - '*' + +jobs: + notification: + runs-on: ubuntu-latest + + steps: + - name: Get the tag short reference + id: get_tag + run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT + + - name: Rocket.Chat Notification + uses: madalozzo/Rocket.Chat.GitHub.Action.Notification@master + with: + type: success + job_name: "[cleverage/doctrine-process-bundle](https://github.com/cleverage/doctrine-process-bundle) : ${{ steps.get_tag.outputs.TAG }} has been released" + url: ${{ secrets.CLEVER_AGE_ROCKET_CHAT_WEBOOK_URL }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..9f1580f --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,62 @@ +name: Quality + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + tools: composer:v2 + - name: Install Composer dependencies (locked) + uses: ramsey/composer-install@v3 + - name: PHPStan + run: vendor/bin/phpstan --no-progress --memory-limit=1G analyse --error-format=github + + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + tools: composer:v2 + - name: Install Composer dependencies (locked) + uses: ramsey/composer-install@v3 + - name: PHP-CS-Fixer + run: vendor/bin/php-cs-fixer fix --diff --dry-run --show-progress=none + + rector: + name: Rector + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + tools: composer:v2 + - name: Install Composer dependencies (locked) + uses: ramsey/composer-install@v3 + - name: Rector + run: vendor/bin/rector --no-progress-bar --dry-run diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2d7e7a4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,74 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + test: + name: PHP ${{ matrix.php-version }} + ${{ matrix.dependencies }} + ${{ matrix.variant }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.allowed-to-fail }} + env: + SYMFONY_REQUIRE: ${{matrix.symfony-require}} + + strategy: + matrix: + php-version: + - '8.2' + - '8.3' + dependencies: [highest] + allowed-to-fail: [false] + symfony-require: [''] + variant: [normal] + include: + - php-version: '8.2' + dependencies: highest + allowed-to-fail: false + symfony-require: 6.4.* + variant: symfony/symfony:"6.4.*" + - php-version: '8.2' + dependencies: highest + allowed-to-fail: false + symfony-require: 7.1.* + variant: symfony/symfony:"7.1.*" + - php-version: '8.3' + dependencies: highest + allowed-to-fail: false + symfony-require: 6.4.* + variant: symfony/symfony:"6.4.*" + - php-version: '8.3' + dependencies: highest + allowed-to-fail: false + symfony-require: 7.1.* + variant: symfony/symfony:"7.1.*" + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: pcov + tools: composer:v2, flex + - name: Add PHPUnit matcher + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Install variant + if: matrix.variant != 'normal' && !startsWith(matrix.variant, 'symfony/symfony') + run: composer require ${{ matrix.variant }} --no-update + - name: Install Composer dependencies (${{ matrix.dependencies }}) + uses: ramsey/composer-install@v3 + with: + dependency-versions: ${{ matrix.dependencies }} + - name: Run Tests with coverage + run: vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml + #- name: Send coverage to Codecov + # uses: codecov/codecov-action@v4 + # with: + # files: build/logs/clover.xml diff --git a/.gitignore b/.gitignore index 81bd528..ca08796 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ /composer.lock /vendor +.env +.idea +/phpunit.xml +.phpunit.result.cache +.phpunit.cache .php-cs-fixer.cache -.idea \ No newline at end of file +coverage-report diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 86007ad..273e26d 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -3,7 +3,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,30 +14,34 @@ } $fileHeaderComment = <<<'EOF' -This file is part of the CleverAge/DoctrineProcessBundle package. + This file is part of the CleverAge/DoctrineProcessBundle package. -Copyright (c) 2017-2023 Clever-Age + Copyright (c) Clever-Age -For the full copyright and license information, please view the LICENSE -file that was distributed with this source code. -EOF; + For the full copyright and license information, please view the LICENSE + file that was distributed with this source code. + EOF; return (new PhpCsFixer\Config()) ->setRules([ '@PHP71Migration' => true, + '@PHP82Migration' => true, '@PHPUnit75Migration:risky' => true, '@Symfony' => true, '@Symfony:risky' => true, + '@DoctrineAnnotation' => true, 'protected_to_private' => false, 'native_constant_invocation' => ['strict' => false], 'header_comment' => ['header' => $fileHeaderComment], 'modernize_strpos' => true, 'get_class_to_class_keyword' => true, + 'phpdoc_to_comment' => ['ignored_tags' => ['var']], // Fix issue on initializeStatement method $params variable ]) ->setRiskyAllowed(true) ->setFinder( (new PhpCsFixer\Finder()) ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') ->append([__FILE__]) ) ->setCacheFile('.php-cs-fixer.cache') diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8b9bda1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +v2.0 +------ + +## BC breaks + +* [#6](https://github.com/cleverage/doctrine-process-bundle/issues/6) Update services according to Symfony best practices. +Services should not use autowiring or autoconfiguration. Instead, all services should be defined explicitly. + Services must be prefixed with the bundle alias instead of using fully qualified class names => `cleverage_doctrine_process` +* [#5](https://github.com/cleverage/doctrine-process-bundle/issues/5) Bump "doctrine/doctrine-bundle": "^2.5" according to Symfony versions supported by `cleverage/process-bundle` +* [#4](https://github.com/cleverage/doctrine-process-bundle/issues/4) Allow installing "doctrine/orm": ^3.0 using at least require "doctrine/orm": "^2.9 || ^3.0". +Forbid "doctrine/dbal" 4 for now (as on "symfony/orm-pack" - symfony/orm-pack@266bae0#diff-d2ab9925cad7eac58e0ff4cc0d251a937ecf49e4b6bf57f8b95aab76648a9d34R7 ) using "doctrine/dbal": "^2.9 || ^3.0". +Add "doctrine/common": "^3.0" and "doctrine/doctrine-migrations-bundle": "^3.2" +* [#4](https://github.com/cleverage/doctrine-process-bundle/issues/4) Remove DoctrineWriterTask option `global_flush` +due to removing [partially flush ability](https://github.com/doctrine/orm/blob/3.0.x/UPGRADE.md#bc-break-removed-ability-to-partially-flushcommit-entity-manager-and-unit-of-work) on `doctrine/orm` 3.* +* [#12](https://github.com/cleverage/doctrine-process-bundle/issues/12) Remove PurgeDoctrineCacheTask + + +### Changes + +* [#3](https://github.com/cleverage/doctrine-process-bundle/issues/3) Add Makefile & .docker for local standalone usage +* [#3](https://github.com/cleverage/doctrine-process-bundle/issues/3) Add rector, phpstan & php-cs-fixer configurations & apply it + +### Fixes + +v2.0-RC1 +------ + +### Changes + +* Miscellaneous changes, show full diff : https://github.com/cleverage/doctrine-process-bundle/compare/v1.0.6...v2.0-RC1 + +v1.0.6 +------ + +### Changes + +* Removing `sidus/base-bundle` dependency + +### Fixes + +* Fixing services.yaml after refactoring + +v1.0.5 +------ + +### Changes + +* Fixed dependencies after removing `sidus/base-bundle` from the base process bundle + +v1.0.4 +------ + +### Fixes + +* Fixed OptionsResolver needing "null" instead of "NULL" +* Fixed backward compatibility break after protected function removal + +v1.0.3 +------ + +### Fixes + +* Fixing update task and allowing to input params properly to both reader and updater tasks + +v1.0.2 +------ + +### Changes + +* Add DoctrineRefresherTask + +v1.0.1 +------ + +### Changes + +* Add "doctrine/doctrine-bundle": "~2.0" dependency + +v1.0.0 +------ + +* Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ef0dbe2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +Contributing +============ + +First of all, **thank you** for contributing, **you are awesome**! + +Here are a few rules to follow in order to ease code reviews, and discussions before +maintainers accept and merge your work. + +You MUST run the quality & test suites. + +You SHOULD write (or update) unit tests. + +You SHOULD write documentation. + +Please, write [commit messages that make sense](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), +and [rebase your branch](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) before submitting your Pull Request. + +One may ask you to [squash your commits](https://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) +too. This is used to "clean" your Pull Request before merging it (we don't want +commits such as `fix tests`, `fix 2`, `fix 3`, etc.). + +Thank you! + +## Running the quality & test suites + +Tests suite uses Docker environments in order to be idempotent to OS's. More than this +PHP version is written inside the Dockerfile; this assures to test the bundle with +the same resources. No need to have PHP installed. + +You only need Docker set it up. + +To allow testing environments more smooth we implemented **Makefile**. +You have two commands available: + +```bash +make quality +``` + +```bash +make tests +``` + +## Deprecations notices + +When a feature should be deprecated, or when you have a breaking change for a future version, please : +* [Fill an issue](https://github.com/cleverage/flysystem-process-bundle/issues/new) +* Add TODO comments with the following format: `@TODO deprecated v2.0` +* Trigger a deprecation error: `@trigger_error('This feature will be deprecated in v2.0', E_USER_DEPRECATED);` + +You can check which deprecation notice is triggered in tests +* `make bash` +* `SYMFONY_DEPRECATIONS_HELPER=0 ./vendor/bin/phpunit` diff --git a/LICENSE b/LICENSE index d32c6b7..045d824 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2023 Clever-Age +Copyright (c) Clever-Age Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 9a2ea98..0a58e32 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,54 @@ .ONESHELL: SHELL := /bin/bash -linter: #[Linter] - vendor/bin/php-cs-fixer fix -phpstan: #[Phpstan] - vendor/bin/phpstan +DOCKER_RUN_PHP = docker compose -f .docker/compose.yaml run --rm php "bash" "-c" +DOCKER_COMPOSE = docker compose -f .docker/compose.yaml + +start: upd #[Global] Start application + +src/vendor: #[Composer] install dependencies + $(DOCKER_RUN_PHP) "composer install --no-interaction" + +upd: #[Docker] Start containers detached + touch .docker/.env + make src/vendor + $(DOCKER_COMPOSE) up --remove-orphans --detach + +up: #[Docker] Start containers + touch .docker/.env + make src/vendor + $(DOCKER_COMPOSE) up --remove-orphans + +stop: #[Docker] Down containers + $(DOCKER_COMPOSE) stop + +down: #[Docker] Down containers + $(DOCKER_COMPOSE) down + +build: #[Docker] Build containers + $(DOCKER_COMPOSE) build + +ps: # [Docker] Show running containers + $(DOCKER_COMPOSE) ps + +bash: #[Docker] Connect to php container with current host user + $(DOCKER_COMPOSE) exec php bash + +logs: #[Docker] Show logs + $(DOCKER_COMPOSE) logs -f + +quality: phpstan php-cs-fixer rector #[Quality] Run all quality checks + +phpstan: #[Quality] Run PHPStan + $(DOCKER_RUN_PHP) "vendor/bin/phpstan --no-progress --memory-limit=1G analyse" + +php-cs-fixer: #[Quality] Run PHP-CS-Fixer + $(DOCKER_RUN_PHP) "vendor/bin/php-cs-fixer fix --diff --verbose" + +rector: #[Quality] Run Rector + $(DOCKER_RUN_PHP) "vendor/bin/rector" + +tests: phpunit #[Tests] Run all tests + +phpunit: #[Tests] Run PHPUnit + $(DOCKER_RUN_PHP) "vendor/bin/phpunit" diff --git a/README.md b/README.md index b9945a4..2fe7d90 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,22 @@ CleverAge/DoctrineProcessBundle ======================= -See process bundle documentation +This bundle is a part of the [CleverAge/ProcessBundle](https://github.com/cleverage/process-bundle) project. +It provides [Doctrine](https://www.doctrine-project.org/) library integration on Process bundle. -- Reference - - Tasks - - Entities - - [DoctrineReaderTask](src/Documentation/reference/task/doctrine_reader_task.md) - - [DoctrineUpdateTask](src/Documentation/reference/task/doctrine_updater_task.md) +Compatible with [Symfony stable version and latest Long-Term Support (LTS) release](https://symfony.com/releases). + +## Documentation + +For usage documentation, see: +[docs/index.md](docs/index.md) + +## Support & Contribution + +For general support and questions, please use [Github](https://github.com/cleverage/doctrine-process-bundle/issues). +If you think you found a bug or you have a feature idea to propose, feel free to open an issue after looking at the [contributing](CONTRIBUTING.md) guide. + +## License + +This bundle is under the MIT license. +For the whole copyright, see the [LICENSE](LICENSE) file distributed with this source code. diff --git a/composer.json b/composer.json index 4e02f68..4afcfe4 100644 --- a/composer.json +++ b/composer.json @@ -39,30 +39,40 @@ } ], "autoload": { - "psr-4": { - "CleverAge\\DoctrineProcessBundle\\": "src/" - } + "psr-4": { + "CleverAge\\DoctrineProcessBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "CleverAge\\DoctrineProcessBundle\\Tests\\": "tests/" + } }, "require": { - "cleverage/process-bundle": "dev-v4-dev", - "doctrine/orm": "^2", - "doctrine/doctrine-bundle": "^2" + "php": ">=8.1", + "cleverage/process-bundle": "^4.0", + "doctrine/common": "^3.0", + "doctrine/dbal": "^2.9 || ^3.0", + "doctrine/doctrine-bundle": "^2.5", + "doctrine/doctrine-migrations-bundle": "^3.2", + "doctrine/orm": "^2.9 || ^3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", - "roave/security-advisories": "dev-latest", - "phpunit/phpunit": "*", + "phpstan/extension-installer": "*", "phpstan/phpstan": "*", "phpstan/phpstan-symfony": "*", - "phpstan/extension-installer": "*", + "phpunit/phpunit": "<10.0", "rector/rector": "*", + "roave/security-advisories": "dev-latest", "symfony/test-pack": "^1.1" }, "config": { - "sort-packages": true, "allow-plugins": { "phpstan/extension-installer": true, - "symfony/flex": true - } + "symfony/flex": true, + "symfony/runtime": true + }, + "sort-packages": true } } diff --git a/config/services/task.yaml b/config/services/task.yaml new file mode 100644 index 0000000..0788a05 --- /dev/null +++ b/config/services/task.yaml @@ -0,0 +1,88 @@ +services: + _defaults: + public: false + tags: + - { name: monolog.logger, channel: cleverage_process_task } + + cleverage_doctrine_process.task.database_reader: + class: CleverAge\DoctrineProcessBundle\Task\Database\DatabaseReaderTask + arguments: + - '@monolog.logger' + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\Database\DatabaseReaderTask: + alias: cleverage_doctrine_process.task.database_reader + public: true + + cleverage_doctrine_process.task.database_updater: + class: CleverAge\DoctrineProcessBundle\Task\Database\DatabaseUpdaterTask + arguments: + - '@doctrine' + - '@monolog.logger' + CleverAge\DoctrineProcessBundle\Task\Database\DatabaseUpdaterTask: + alias: cleverage_doctrine_process.task.database_updater + public: true + + cleverage_doctrine_process.task.doctrine_clear_entity_manager: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\ClearEntityManagerTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\ClearEntityManagerTask: + alias: cleverage_doctrine_process.task.doctrine_clear_entity_manager + public: true + + cleverage_doctrine_process.task.doctrine_batch_writer: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineBatchWriterTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineBatchWriterTask: + alias: cleverage_doctrine_process.task.doctrine_batch_writer + public: true + + cleverage_doctrine_process.task.doctrine_cleaner: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineCleanerTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineCleanerTask: + alias: cleverage_doctrine_process.task.doctrine_cleaner + public: true + + cleverage_doctrine_process.task.doctrine_detacher: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineDetacherTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineDetacherTask: + alias: cleverage_doctrine_process.task.doctrine_detacher + public: true + + cleverage_doctrine_process.task.doctrine_reader: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineReaderTask + arguments: + - '@monolog.logger' + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineReaderTask: + alias: cleverage_doctrine_process.task.doctrine_reader + public: true + + cleverage_doctrine_process.task.doctrine_refresher: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRefresherTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRefresherTask: + alias: cleverage_doctrine_process.task.doctrine_refresher + public: true + + cleverage_doctrine_process.task.doctrine_remover: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRemoverTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRemoverTask: + alias: cleverage_doctrine_process.task.doctrine_remover + public: true + + cleverage_doctrine_process.task.doctrine_writer: + class: CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineWriterTask + arguments: + - '@doctrine' + CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineWriterTask: + alias: cleverage_doctrine_process.task.doctrine_writer + public: true diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9c24dd1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,34 @@ +## Prerequisite + +CleverAge/ProcessBundle must be [installed](https://github.com/cleverage/process-bundle/blob/main/docs/01-quick_start.md#installation). + +## Installation + +Make sure Composer is installed globally, as explained in the [installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + +Open a command console, enter your project directory and install it using composer: + +```bash +composer require cleverage/doctrine-process-bundle +``` + +Remember to add the following line to config/bundles.php (not required if Symfony Flex is used) + +```php +CleverAge\DoctrineProcessBundle\CleverAgeDoctrineProcessBundle::class => ['all' => true], +``` + +## Reference + +- Tasks + - [DatabaseReaderTask](reference/tasks/database_reader_task.md) + - [DatabaseUpdaterTask](reference/tasks/database_updater_task.md) + - [ClearEntityManagerTask](reference/tasks/doctrine_clear_task.md)) + - [DoctrineBatchWriterTask](reference/tasks/doctrine_batchwriter_task.md) + - [DoctrineCleanerTask](reference/tasks/doctrine_cleaner_task.md) + - [DoctrineDetacherTask](reference/tasks/doctrine_detacher_task.md) + - [DoctrineReaderTask](reference/tasks/doctrine_reader_task.md) + - [DoctrineRefresherTask](reference/tasks/doctrine_refresher_task.md) + - [DoctrineRemoverTask](reference/tasks/doctrine_remover_task.md) + - [DoctrineWriterTask](reference/tasks/doctrine_writer_task.md) diff --git a/docs/reference/tasks/_template.md b/docs/reference/tasks/_template.md new file mode 100644 index 0000000..ed1d4a5 --- /dev/null +++ b/docs/reference/tasks/_template.md @@ -0,0 +1,44 @@ +TaskName +======== + +_Describe main goal an use cases of the task_ + +Task reference +-------------- + +* **Service**: `ClassName` + +Accepted inputs +--------------- + +_Description of allowed types_ + +Possible outputs +---------------- + +_Description of possible types_ + +Options +------- + +| Code | Type | Required | Default | Description | +| ---- | ---- | :------: | ------- | ----------- | +| `code` | `type` | **X** _or nothing_ | `default value` _if available_ | _description_ | + +Examples +-------- + +_YAML samples and explanations_ + +* Example 1 + - details + - details + +```yaml +# Task configuration level +code: + service: '@service_ref' + options: + a: 1 + b: 2 +``` diff --git a/docs/reference/tasks/database_reader_task.md b/docs/reference/tasks/database_reader_task.md new file mode 100644 index 0000000..ad77b44 --- /dev/null +++ b/docs/reference/tasks/database_reader_task.md @@ -0,0 +1,53 @@ +DatabaseReaderTask +================== + +Reads data from a database. + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\Database\DatabaseReaderTask` +* **Iterable task** + +Accepted inputs +--------------- + +`array`or `None`: Input can be used as the query params if needed + +Possible outputs +---------------- + +`array`: Rows returned by the query. + +Options +------- + +| Code | Type | Required | Default | Description | +|-------------------|--------------------|:--------:|-----------|---------------------------------------------------------------------------| +| `connection` | `string` | | `null` | Doctrine connection (default if not specified) | +| `table` | `string` | **X** | `[]` | Table of the query | +| `sql` | `string` | | `null` | Query to execute (if not specified then: "select tbl.* from `table` tbl") | +| `limit` | `int` or `null` | | `null` | Result max count | +| `offset` | `int` or `null` | | `null` | Result first item offset | +| `paginate` | `int` or `null` | | `null` | Paginate the results | +| `input_as_params` | `bool` | | `false` | Use the input as params | +| `params` | `array` | | `[]` | Query params | +| `types` | `array` | | `[]` | Query params types | +| `empty_log_level` | `string` or `null` | | `warning` | Log level if the result set is empty | + + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\Database\DatabaseReaderTask' + options: + table: 'book' + limit: 10 + offset: 3 + params: + title: "IT" + empty_log_level: debug +``` \ No newline at end of file diff --git a/docs/reference/tasks/database_updater_task.md b/docs/reference/tasks/database_updater_task.md new file mode 100644 index 0000000..29a342a --- /dev/null +++ b/docs/reference/tasks/database_updater_task.md @@ -0,0 +1,45 @@ +DatabaseReaderTask +================== + +Writes data to a database. + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\Database\DatabaseUpdaterTask` + +Accepted inputs +--------------- + +`array`or `None`: Input can be used as the query params if needed + +Possible outputs +---------------- + +`int`: Number of rows changed by the query. + +Options +------- + +| Code | Type | Required | Default | Description | +|-------------------|--------------------|:--------:|-----------|------------------------------------------------| +| `connection` | `string` | | `null` | Doctrine connection (default if not specified) | +| `sql` | `string` | **X** | `null` | Query to execute | +| `input_as_params` | `bool` | | `false` | Use the input as params | +| `params` | `array` | | `[]` | Query params | +| `types` | `array` | | `[]` | Query params types | + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\Database\DatabaseUpdaterTask' + options: + sql: 'update author set firstname = :firstname, lastname = :lastname' + input_as_params: false + params: + firstname: 'Pascal' + lastname: 'Dupont' +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_batchwriter_task.md b/docs/reference/tasks/doctrine_batchwriter_task.md new file mode 100644 index 0000000..6080547 --- /dev/null +++ b/docs/reference/tasks/doctrine_batchwriter_task.md @@ -0,0 +1,37 @@ +DoctrineBatchWriterTask +======================= + +Writes multiple entities to a database. + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineBatchWriterTask` + +Accepted inputs +--------------- + +`array`: Entities to be persisted in the database + +Possible outputs +---------------- + +`array`: Batch of the entities persisted to the database + +Options +------- + +| Code | Type | Required | Default | Description | +|---------------|-------|:--------:|---------|-------------| +| `batch_count` | `int` | | `10` | Batch size | + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineBatchWriterTask' + options: + batch_count: 2 +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_cleaner_task.md b/docs/reference/tasks/doctrine_cleaner_task.md new file mode 100644 index 0000000..aa32c66 --- /dev/null +++ b/docs/reference/tasks/doctrine_cleaner_task.md @@ -0,0 +1,33 @@ +DoctrineCleanerTask +==================== + +Clear the entity manager of an entity. + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineCleanerTask` + +Accepted inputs +--------------- + +`object`: Entity to be persisted in the database + +Possible outputs +---------------- + +None + +Options +------- + +None + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineCleanerTask' +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_clear_task.md b/docs/reference/tasks/doctrine_clear_task.md new file mode 100644 index 0000000..8343304 --- /dev/null +++ b/docs/reference/tasks/doctrine_clear_task.md @@ -0,0 +1,37 @@ +ClearEntityManagerTask +====================== + +Clear the entity manager. + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\ClearEntityManagerTask` + +Accepted inputs +--------------- + +`None` + +Possible outputs +---------------- + +`None` + +Options +------- + +| Code | Type | Required | Default | Description | +|------------------|--------------------|:--------:|---------|---------------------------------------------| +| `entity_manager` | `string` or `null` | | `null` | Use another entity manager than the default | + + + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\ClearEntityManagerTask' +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_detacher_task.md b/docs/reference/tasks/doctrine_detacher_task.md new file mode 100644 index 0000000..5fdb9eb --- /dev/null +++ b/docs/reference/tasks/doctrine_detacher_task.md @@ -0,0 +1,33 @@ +DoctrineDetacherTask +==================== + +Detach a Doctrine entity from the entity manager + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineDetacherTask` + +Accepted inputs +--------------- + +`object`: Doctrine managed entity + +Possible outputs +---------------- + +`None` + +Options +------- + +`None` + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineDetacherTask' +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_reader_task.md b/docs/reference/tasks/doctrine_reader_task.md new file mode 100644 index 0000000..5e054f8 --- /dev/null +++ b/docs/reference/tasks/doctrine_reader_task.md @@ -0,0 +1,50 @@ +DoctrineReaderTask +================== + +Reads Doctrine entity from a repository + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineReaderTask` + +Accepted inputs +--------------- + +`None` + +Possible outputs +---------------- + +`array`: Result set of the entities + +Options +------- + + +| Code | Type | Required | Default | Description | +|-------------------|--------------------|:--------:|-----------|------------------------------------------------| +| `class_name` | `string` | **X** | `null` | Name of the class (e.g. : 'App\Entity\Author') | +| `criteria` | `array` | | `[]` | Criteria of the query | +| `order_by` | `array` | | `[]` | Order by of the query | +| `limit` | `int` or `null` | | `null` | Result max count | +| `offset` | `int` or `null` | | `null` | Result first item offset | +| `empty_log_level` | `string` or `null` | | `warning` | Log level if the result set is empty | + + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineReaderTask' + options: + class_name: 'App\Entity\Author' + criteria: + lastname: 'King' + order_by: + lastname: 'asc' + limit: 5 + offset: 3 +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_refresher_task.md b/docs/reference/tasks/doctrine_refresher_task.md new file mode 100644 index 0000000..d56c428 --- /dev/null +++ b/docs/reference/tasks/doctrine_refresher_task.md @@ -0,0 +1,33 @@ +DoctrineRefresherTask +===================== + +Refreshes a Doctrine entity from the entity manager + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRefresherTask` + +Accepted inputs +--------------- + +`object`: Doctrine managed entity + +Possible outputs +---------------- + +`object`: The refreshed entity + +Options +------- + +None + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRefresherTask' +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_remover_task.md b/docs/reference/tasks/doctrine_remover_task.md new file mode 100644 index 0000000..435f126 --- /dev/null +++ b/docs/reference/tasks/doctrine_remover_task.md @@ -0,0 +1,33 @@ +DoctrineRemoverTask +=================== + +Removes a Doctrine entity from the entity manager then flushes + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRemoverTask` + +Accepted inputs +--------------- + +`object`: Doctrine managed entity + +Possible outputs +---------------- + +`None` + +Options +------- + +`None` + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineRemoverTask' +``` \ No newline at end of file diff --git a/docs/reference/tasks/doctrine_writer_task.md b/docs/reference/tasks/doctrine_writer_task.md new file mode 100644 index 0000000..5eb42a3 --- /dev/null +++ b/docs/reference/tasks/doctrine_writer_task.md @@ -0,0 +1,33 @@ +DoctrineWriterTask +================== + +Writes a Doctrine entity to the database. + +Task reference +-------------- + +* **Service**: `CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineWriterTask` + +Accepted inputs +--------------- + +`object`: Doctrine managed entity + +Possible outputs +---------------- + +`object`: Re-outputs given entity + +Options +------- + +`None` + +Example +------- + +```yaml +# Task configuration level +code: + service: '@CleverAge\DoctrineProcessBundle\Task\EntityManager\DoctrineWriterTask' +``` \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index a9f5c7b..e9a9e7e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,24 +1,5 @@ parameters: - level: 6 + level: 10 paths: - src - excludePaths: - - ecs.php - - vendor/* - - tests/* - - rector.php - - var/* - - src/Resources/tests/* - ignoreErrors: - - '#type has no value type specified in iterable type#' - - '#has parameter .* with no value type specified in iterable type#' - - '#has no value type specified in iterable type array#' - - '#configureOptions\(\) has no return type specified.#' - - '#configure\(\) has no return type specified#' - - '#process\(\) has no return type specified#' - - '#should return Iterator but returns Traversable#' - - '#Negated boolean expression is always false#' - checkGenericClassInNonGenericObjectType: false - reportUnmatchedIgnoredErrors: false - inferPrivatePropertyTypeFromConstructor: true - treatPhpDocTypesAsCertain: false \ No newline at end of file + - tests diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..766495c --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + tests + + + + + + src + + + diff --git a/rector.php b/rector.php index 36408f0..cb382c4 100644 --- a/rector.php +++ b/rector.php @@ -3,23 +3,30 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Rector\Core\ValueObject\PhpVersion; +use Rector\Doctrine\Set\DoctrineSetList; use Rector\Set\ValueObject\LevelSetList; -use Rector\Set\ValueObject\SetList; -use Rector\Symfony\Set\SymfonyLevelSetList; +use Rector\Symfony\Set\SymfonySetList; +use Rector\ValueObject\PhpVersion; -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->parallel(); - $rectorConfig->importNames(); - $rectorConfig->importShortClasses(); - - $rectorConfig->paths([__DIR__.'/src']); - - $rectorConfig->sets([ - SetList::TYPE_DECLARATION, +return RectorConfig::configure() + ->withPhpVersion(PhpVersion::PHP_82) + ->withPaths([ + __DIR__.'/src', + __DIR__.'/tests', + ]) + ->withPhpSets(php82: true) + // here we can define, what prepared sets of rules will be applied + ->withPreparedSets( + deadCode: true, + codeQuality: true + ) + ->withSets([ LevelSetList::UP_TO_PHP_82, - SymfonyLevelSetList::UP_TO_SYMFONY_63, - ]); - - $rectorConfig->phpVersion(PhpVersion::PHP_82); -}; + SymfonySetList::SYMFONY_64, + SymfonySetList::SYMFONY_71, + SymfonySetList::SYMFONY_CODE_QUALITY, + SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, + SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, + DoctrineSetList::DOCTRINE_CODE_QUALITY, + ]) +; diff --git a/src/CleverAgeDoctrineProcessBundle.php b/src/CleverAgeDoctrineProcessBundle.php index 804170e..d013ee1 100644 --- a/src/CleverAgeDoctrineProcessBundle.php +++ b/src/CleverAgeDoctrineProcessBundle.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,13 +15,10 @@ use Symfony\Component\HttpKernel\Bundle\Bundle; -/** - * Class CleverAgeDoctrineProcessBundle. - * - * @author Valentin Clavreul - * @author Vincent Chalnot - * @author Madeline Veyrenc - */ class CleverAgeDoctrineProcessBundle extends Bundle { + public function getPath(): string + { + return \dirname(__DIR__); + } } diff --git a/src/DependencyInjection/CleverAgeDoctrineProcessExtension.php b/src/DependencyInjection/CleverAgeDoctrineProcessExtension.php index eef3144..6bf6e07 100644 --- a/src/DependencyInjection/CleverAgeDoctrineProcessExtension.php +++ b/src/DependencyInjection/CleverAgeDoctrineProcessExtension.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,6 +16,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** @@ -27,7 +28,20 @@ class CleverAgeDoctrineProcessExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { - $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.yaml'); + $this->findServices($container, __DIR__.'/../../config/services'); + } + + /** + * Recursively import config files into container. + */ + protected function findServices(ContainerBuilder $container, string $path, string $extension = 'yaml'): void + { + $finder = new Finder(); + $finder->in($path) + ->name('*.'.$extension)->files(); + $loader = new YamlFileLoader($container, new FileLocator($path)); + foreach ($finder as $file) { + $loader->load($file->getFilename()); + } } } diff --git a/src/Documentation/reference/task/doctrine_reader_task.md b/src/Documentation/reference/task/doctrine_reader_task.md deleted file mode 100644 index 3cb1bc4..0000000 --- a/src/Documentation/reference/task/doctrine_reader_task.md +++ /dev/null @@ -1,36 +0,0 @@ -DoctrineReaderTask -================== - -Reads data from a Doctrine Repository. - -Task reference --------------- - -* **Service**: `CleverAge\DoctrineProcessBundle\Task\Database\DatabaseReaderTask` -* **Iterable task** - -Accepted inputs ---------------- - -Input is ignored - -Possible outputs ----------------- - -Iterate on an entity list returned by a Doctrine query. - -Options -------- - -| Code | Type | Required | Default | Description | -|------------------| ---- | :------: | ------- |-------------------------------------------------------| -| `table` | `string` | **X** | | Table | -| `params` | `array` | | `[]` | List of field => value to use while matching entities | -| `limit` | `int` or `null` | | `null` | Result max count | -| `offset` | `int` or `null` | | `null` | Result first item offset | -| `entity_manager` | `string` or `null` | | `null` | Use another entity manager than the default | - -Example -------- - -https://github.com/cleverage/process-bundle-ui-demo/blob/main/config/packages/process/demo.doctrine.read.yaml \ No newline at end of file diff --git a/src/Documentation/reference/task/doctrine_updater_task.md b/src/Documentation/reference/task/doctrine_updater_task.md deleted file mode 100644 index d0b24b6..0000000 --- a/src/Documentation/reference/task/doctrine_updater_task.md +++ /dev/null @@ -1,28 +0,0 @@ -DoctrineWriterTask -================== - -Write a Doctrine entity to the database. - -Task reference --------------- - -* **Service**: `CleverAge\DoctrineProcessBundle\Task\Database\DatabaseUpdaterTask` - -Accepted inputs ---------------- - -Any doctrine managed entity. - -Possible outputs ----------------- - -Re-output given entity. - -Options -------- - -| Code | Type | Required | Default | Description | -| ---- | ---- | :------: | ------- | ----------- | -| `entity_manager` | `string` or `null` | | `null` | Use another entity manager than the default | -| `global_flush` | `bool` | | `true` | Flush the whole entity manager after persist | - diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml deleted file mode 100644 index 56d8784..0000000 --- a/src/Resources/config/services.yaml +++ /dev/null @@ -1,8 +0,0 @@ -services: - CleverAge\DoctrineProcessBundle\Task\: - resource: '../../Task/' - autowire: true - public: true - shared: false - tags: - - { name: monolog.logger, channel: cleverage_process_task } diff --git a/src/Task/Database/DatabaseReaderTask.php b/src/Task/Database/DatabaseReaderTask.php index e31bdf8..639f2c0 100644 --- a/src/Task/Database/DatabaseReaderTask.php +++ b/src/Task/Database/DatabaseReaderTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -19,6 +19,7 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Result; +use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; @@ -26,6 +27,18 @@ /** * Fetch entities from doctrine. + * + * @phpstan-type Options array{ + * 'sql': ?string, + * 'table': string, + * 'limit': ?int, + * 'empty_log_level': string, + * 'paginate': ?int, + * 'offset': ?int, + * 'input_as_params': bool, + * 'params': array, + * 'types': array|array + * } */ class DatabaseReaderTask extends AbstractConfigurableTask implements IterableTaskInterface, FinalizableTaskInterface { @@ -35,18 +48,18 @@ class DatabaseReaderTask extends AbstractConfigurableTask implements IterableTas public function __construct( protected LoggerInterface $logger, - protected ManagerRegistry $doctrine + protected ManagerRegistry $doctrine, ) { } /** * Moves the internal pointer to the next element, * return true if the task has a next element - * return false if the task has terminated it's iteration. + * return false if the task has terminated its iteration. */ public function next(ProcessState $state): bool { - if (!$this->statement) { + if (!$this->statement instanceof Result) { return false; } @@ -57,8 +70,9 @@ public function next(ProcessState $state): bool public function execute(ProcessState $state): void { + /** @var Options $options */ $options = $this->getOptions($state); - if (!$this->statement) { + if (!$this->statement instanceof Result) { $this->statement = $this->initializeStatement($state); } @@ -102,6 +116,7 @@ public function finalize(ProcessState $state): void protected function initializeStatement(ProcessState $state): Result { + /** @var Options $options */ $options = $this->getOptions($state); $connection = $this->getConnection($state); $sql = $options['sql']; @@ -121,12 +136,14 @@ protected function initializeStatement(ProcessState $state): Result $sql = $qb->getSQL(); } - if ($options['input_as_params']) { - $params = $state->getInput(); - } else { - $params = $options['params']; + + $inputAsParams = $state->getInput(); + $params = $options['input_as_params'] ? $inputAsParams : $options['params']; + if (!\is_array($params)) { + throw new \UnexpectedValueException('Expecting an array of params'); } + /** @var array $params */ return $connection->executeQuery($sql, $params, $options['types']); } @@ -172,7 +189,11 @@ protected function configureOptions(OptionsResolver $resolver): void protected function getConnection(ProcessState $state): Connection { - /* @noinspection PhpIncompatibleReturnTypeInspection */ - return $this->doctrine->getConnection($this->getOption($state, 'connection')); + /** @var ?string $connectionOptions */ + $connectionOptions = $this->getOption($state, 'connection'); + /** @var Connection $connection */ + $connection = $this->doctrine->getConnection($connectionOptions); + + return $connection; } } diff --git a/src/Task/Database/DatabaseUpdaterTask.php b/src/Task/Database/DatabaseUpdaterTask.php index c52a822..1d6df62 100644 --- a/src/Task/Database/DatabaseUpdaterTask.php +++ b/src/Task/Database/DatabaseUpdaterTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,6 +17,7 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -25,12 +26,19 @@ * Execute an update/delete in the database from a SQL statement. * * @see https://www.doctrine-project.org/projects/doctrine-dbal/en/2.9/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion + * + * @phpstan-type Options array{ + * 'sql': string, + * 'input_as_params': bool, + * 'params': array, + * 'types': array|array + * } */ class DatabaseUpdaterTask extends AbstractConfigurableTask { public function __construct( protected ManagerRegistry $doctrine, - protected LoggerInterface $logger + protected LoggerInterface $logger, ) { } @@ -48,19 +56,18 @@ public function execute(ProcessState $state): void */ protected function initializeStatement(ProcessState $state): int { + /** @var Options $options */ $options = $this->getOptions($state); $connection = $this->getConnection($state); - if ($options['input_as_params']) { - $params = $state->getInput(); - } else { - $params = $options['params']; - } + $inputAsParams = $state->getInput(); + $params = $options['input_as_params'] ? $inputAsParams : $options['params']; if (!\is_array($params)) { throw new \UnexpectedValueException('Expecting an array of params'); } - return $connection->executeStatement($options['sql'], $params, $options['types']); + /** @var array $params */ + return (int) $connection->executeStatement($options['sql'], $params, $options['types']); } protected function configureOptions(OptionsResolver $resolver): void @@ -81,7 +88,11 @@ protected function configureOptions(OptionsResolver $resolver): void protected function getConnection(ProcessState $state): Connection { - /* @noinspection PhpIncompatibleReturnTypeInspection */ - return $this->doctrine->getConnection($this->getOption($state, 'connection')); + /** @var ?string $connectionOptions */ + $connectionOptions = $this->getOption($state, 'connection'); + /** @var Connection $connection */ + $connection = $this->doctrine->getConnection($connectionOptions); + + return $connection; } } diff --git a/src/Task/EntityManager/AbstractDoctrineQueryTask.php b/src/Task/EntityManager/AbstractDoctrineQueryTask.php index f775dde..ef26c21 100644 --- a/src/Task/EntityManager/AbstractDoctrineQueryTask.php +++ b/src/Task/EntityManager/AbstractDoctrineQueryTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -20,6 +20,15 @@ /** * Easily extendable task to query entities in their repository. + * + * @phpstan-type Options array{ + * 'class_name': class-string, + * 'criteria': array|null>, + * 'order_by': array, + * 'limit': ?int, + * 'offset': ?int, + * 'empty_log_level': string, + * } */ abstract class AbstractDoctrineQueryTask extends AbstractDoctrineTask { @@ -56,19 +65,26 @@ protected function configureOptions(OptionsResolver $resolver): void ); } + /** + * @template TEntityClass of object + * + * @param EntityRepository $repository + * @param array|null> $criteria + * @param array $orderBy + */ protected function getQueryBuilder( EntityRepository $repository, array $criteria, array $orderBy, - int $limit = null, - int $offset = null + ?int $limit = null, + ?int $offset = null, ): QueryBuilder { $qb = $repository->createQueryBuilder('e'); foreach ($criteria as $field => $value) { if (preg_match('/[^a-zA-Z0-9]/', $field)) { throw new \UnexpectedValueException("Forbidden field name '{$field}'"); } - $parameterName = uniqid('param', true); + $parameterName = 'param_'.bin2hex(random_bytes(4)); if (null === $value) { $qb->andWhere("e.{$field} IS null"); } else { @@ -80,7 +96,6 @@ protected function getQueryBuilder( $qb->setParameter($parameterName, $value); } } - /* @noinspection ForeachSourceInspection */ foreach ($orderBy as $field => $order) { $qb->addOrderBy("e.{$field}", $order); } diff --git a/src/Task/EntityManager/AbstractDoctrineTask.php b/src/Task/EntityManager/AbstractDoctrineTask.php index 0a20a67..397db62 100644 --- a/src/Task/EntityManager/AbstractDoctrineTask.php +++ b/src/Task/EntityManager/AbstractDoctrineTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,8 +15,8 @@ use CleverAge\ProcessBundle\Model\AbstractConfigurableTask; use CleverAge\ProcessBundle\Model\ProcessState; -use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -25,7 +25,7 @@ abstract class AbstractDoctrineTask extends AbstractConfigurableTask { public function __construct( - protected ManagerRegistry $doctrine + protected ManagerRegistry $doctrine, ) { } @@ -37,8 +37,11 @@ protected function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('entity_manager', ['null', 'string']); } - protected function getManager(ProcessState $state): EntityManagerInterface + protected function getManager(ProcessState $state): ObjectManager { - return $this->doctrine->getManager($this->getOption($state, 'entity_manager')); + /** @var ?string $entityManagerName */ + $entityManagerName = $this->getOption($state, 'entity_manager'); + + return $this->doctrine->getManager($entityManagerName); } } diff --git a/src/Task/EntityManager/ClearEntityManagerTask.php b/src/Task/EntityManager/ClearEntityManagerTask.php index 4a733d3..4c8ea7f 100644 --- a/src/Task/EntityManager/ClearEntityManagerTask.php +++ b/src/Task/EntityManager/ClearEntityManagerTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/Task/EntityManager/DoctrineBatchWriterTask.php b/src/Task/EntityManager/DoctrineBatchWriterTask.php index 680d38b..f26bdf5 100644 --- a/src/Task/EntityManager/DoctrineBatchWriterTask.php +++ b/src/Task/EntityManager/DoctrineBatchWriterTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -24,6 +24,7 @@ */ class DoctrineBatchWriterTask extends AbstractDoctrineTask implements FlushableTaskInterface { + /** @var array */ protected array $batch = []; public function flush(ProcessState $state): void @@ -33,7 +34,9 @@ public function flush(ProcessState $state): void public function execute(ProcessState $state): void { - $this->batch[] = $state->getInput(); + /** @var object $input */ + $input = $state->getInput(); + $this->batch[] = $input; if (\count($this->batch) >= $this->getOption($state, 'batch_count')) { $this->writeBatch($state); @@ -53,13 +56,14 @@ protected function configureOptions(OptionsResolver $resolver): void protected function writeBatch(ProcessState $state): void { - if (0 === \count($this->batch)) { + if ([] === $this->batch) { $state->setSkipped(true); return; } // Support for multiple entity managers is overkill but might be necessary + /** @var \SplObjectStorage $entityManagers */ $entityManagers = new \SplObjectStorage(); foreach ($this->batch as $entity) { $class = ClassUtils::getClass($entity); diff --git a/src/Task/EntityManager/DoctrineCleanerTask.php b/src/Task/EntityManager/DoctrineCleanerTask.php index dab980b..55d7fba 100644 --- a/src/Task/EntityManager/DoctrineCleanerTask.php +++ b/src/Task/EntityManager/DoctrineCleanerTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -26,8 +26,9 @@ public function execute(ProcessState $state): void { $entity = $state->getInput(); if (null === $entity) { - throw new \RuntimeException('DoctrineWriterTask does not allow null input'); + throw new \RuntimeException('DoctrineCleanerTask does not allow null input'); } + /** @var object $entity */ $class = ClassUtils::getClass($entity); $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { diff --git a/src/Task/EntityManager/DoctrineDetacherTask.php b/src/Task/EntityManager/DoctrineDetacherTask.php index a303299..a72fa5b 100644 --- a/src/Task/EntityManager/DoctrineDetacherTask.php +++ b/src/Task/EntityManager/DoctrineDetacherTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -28,6 +28,7 @@ public function execute(ProcessState $state): void if (null === $entity) { throw new \RuntimeException('DoctrineWriterTask does not allow null input'); } + /** @var object $entity */ $class = ClassUtils::getClass($entity); $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { diff --git a/src/Task/EntityManager/DoctrineReaderTask.php b/src/Task/EntityManager/DoctrineReaderTask.php index c704803..2b4ce1c 100644 --- a/src/Task/EntityManager/DoctrineReaderTask.php +++ b/src/Task/EntityManager/DoctrineReaderTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,20 +17,21 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\Internal\Hydration\IterableResult; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; /** * Fetch entities from doctrine. + * + * @phpstan-import-type Options from AbstractDoctrineQueryTask */ class DoctrineReaderTask extends AbstractDoctrineQueryTask implements IterableTaskInterface { - protected ?IterableResult $iterator = null; + protected ?\Iterator $iterator = null; public function __construct( protected LoggerInterface $logger, - ManagerRegistry $doctrine + ManagerRegistry $doctrine, ) { parent::__construct($doctrine); } @@ -42,7 +43,7 @@ public function __construct( */ public function next(ProcessState $state): bool { - if (!$this->iterator) { + if (!$this->iterator instanceof \Iterator) { return false; } $this->iterator->next(); @@ -52,24 +53,24 @@ public function next(ProcessState $state): bool public function execute(ProcessState $state): void { + /** @var Options $options */ $options = $this->getOptions($state); - if (!$this->iterator) { + if (!$this->iterator instanceof \Iterator) { + /** @var class-string $class */ $class = $options['class_name']; $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { throw new \UnexpectedValueException("No manager found for class {$class}"); } $repository = $entityManager->getRepository($class); - if (!$repository instanceof EntityRepository) { - throw new \UnexpectedValueException("No repository found for class {$class}"); - } $this->initIterator($repository, $options); } - - $result = $this->iterator->current(); + if ($this->iterator instanceof \Iterator) { + $result = $this->iterator->current(); + } // Handle empty results - if (false === $result) { + if (!isset($result) || false === $result) { $logContext = [ 'options' => $options, ]; @@ -80,9 +81,15 @@ public function execute(ProcessState $state): void return; } - $state->setOutput(reset($result)); + $state->setOutput($result); } + /** + * @template TEntityClass of object + * + * @param EntityRepository $repository + * @param Options $options + */ protected function initIterator(EntityRepository $repository, array $options): void { $qb = $this->getQueryBuilder( @@ -93,8 +100,7 @@ protected function initIterator(EntityRepository $repository, array $options): v $options['offset'] ); - $this->iterator = $qb->getQuery() - ->iterate(); - $this->iterator->next(); // Move to first element + $this->iterator = new \ArrayIterator(iterator_to_array($qb->getQuery()->toIterable())); + $this->iterator->rewind(); } } diff --git a/src/Task/EntityManager/DoctrineRefresherTask.php b/src/Task/EntityManager/DoctrineRefresherTask.php index 8df6dbb..ebfa855 100644 --- a/src/Task/EntityManager/DoctrineRefresherTask.php +++ b/src/Task/EntityManager/DoctrineRefresherTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -18,7 +18,7 @@ use Doctrine\ORM\EntityManagerInterface; /** - * Detach Doctrine entities from unit of work. + * Refreshes a Doctrine entity from the database. */ class DoctrineRefresherTask extends AbstractDoctrineTask { @@ -28,6 +28,7 @@ public function execute(ProcessState $state): void if (null === $entity) { throw new \RuntimeException('DoctrineRefresherTask does not allow null input'); } + /** @var object $entity */ $class = ClassUtils::getClass($entity); $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { diff --git a/src/Task/EntityManager/DoctrineRemoverTask.php b/src/Task/EntityManager/DoctrineRemoverTask.php index 08c2329..1be5850 100644 --- a/src/Task/EntityManager/DoctrineRemoverTask.php +++ b/src/Task/EntityManager/DoctrineRemoverTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -25,6 +25,7 @@ class DoctrineRemoverTask extends AbstractDoctrineTask public function execute(ProcessState $state): void { $entity = $state->getInput(); + /** @var object $entity */ $class = ClassUtils::getClass($entity); $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { diff --git a/src/Task/EntityManager/DoctrineWriterTask.php b/src/Task/EntityManager/DoctrineWriterTask.php index 76eb366..f069376 100644 --- a/src/Task/EntityManager/DoctrineWriterTask.php +++ b/src/Task/EntityManager/DoctrineWriterTask.php @@ -5,7 +5,7 @@ /* * This file is part of the CleverAge/DoctrineProcessBundle package. * - * Copyright (c) 2017-2023 Clever-Age + * Copyright (c) Clever-Age * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,7 +16,6 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; /** * Persists and flush Doctrine entities. @@ -28,18 +27,10 @@ public function execute(ProcessState $state): void $state->setOutput($this->writeEntity($state)); } - protected function configureOptions(OptionsResolver $resolver): void - { - parent::configureOptions($resolver); - $resolver->setDefaults([ - 'global_flush' => true, - ]); - $resolver->setAllowedTypes('global_flush', ['boolean']); - } - protected function writeEntity(ProcessState $state): mixed { - $options = $this->getOptions($state); + $this->getOptions($state); + /** @var ?object $entity */ $entity = $state->getInput(); if (null === $entity) { @@ -52,11 +43,7 @@ protected function writeEntity(ProcessState $state): mixed } $entityManager->persist($entity); - if ($options['global_flush']) { - $entityManager->flush(); - } else { - $entityManager->flush($entity); - } + $entityManager->flush(); return $entity; } diff --git a/src/Task/EntityManager/PurgeDoctrineCacheTask.php b/src/Task/EntityManager/PurgeDoctrineCacheTask.php deleted file mode 100644 index 3bc3e7a..0000000 --- a/src/Task/EntityManager/PurgeDoctrineCacheTask.php +++ /dev/null @@ -1,108 +0,0 @@ - 'getQueryCacheImpl', - 'result_cache' => 'getResultCacheImpl', - 'hydration_cache' => 'getHydrationCacheImpl', - 'metadata_cache' => 'getMetadataCacheImpl', - ]; - - public function __construct( - protected ManagerRegistry $doctrine - ) { - } - - public function execute(ProcessState $state): void - { - $entityManager = $this->getOption($state, 'entity_manager'); - if ($entityManager) { - $this->purgeEntityManagerCache($entityManager, $state); - } else { - foreach ($this->doctrine->getManagers() as $entityManager) { - if ($entityManager instanceof EntityManagerInterface) { - $this->purgeEntityManagerCache($entityManager, $state); - } - } - } - } - - protected function purgeEntityManagerCache(EntityManagerInterface $entityManager, ProcessState $state): void - { - $options = $this->getOptions($state); - foreach (self::METHOD_MAP as $option => $method) { - if ($options[$option]) { - $this->purgeCache($entityManager->getConfiguration()->{$method}()); - } - } - } - - protected function purgeCache(Cache $cache = null): void - { - if ($cache instanceof FlushableCache) { - $cache->flushAll(); - } - } - - protected function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults( - [ - 'query_cache' => true, - 'result_cache' => true, - 'hydration_cache' => true, - 'metadata_cache' => false, - 'entity_manager' => null, // Purge all entity managers by default - ] - ); - $resolver->setAllowedTypes('query_cache', ['bool']); - $resolver->setAllowedTypes('result_cache', ['bool']); - $resolver->setAllowedTypes('hydration_cache', ['bool']); - $resolver->setAllowedTypes('entity_manager', ['null', 'string', EntityManagerInterface::class]); - $resolver->setNormalizer( - 'entity_manager', - function (Options $options, $value): ?EntityManagerInterface { - if (null === $value) { - return null; - } - if (\is_string($value)) { - $value = $this->doctrine->getManager($value); - } - if (!$value instanceof EntityManagerInterface) { - throw new \UnexpectedValueException('Unable to resolve entity manager'); - } - - return $value; - } - ); - } -} diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29