diff --git a/.docker/compose.yaml b/.docker/compose.yaml new file mode 100644 index 0000000..436a718 --- /dev/null +++ b/.docker/compose.yaml @@ -0,0 +1,14 @@ +x-build-args: &build-args + UID: "${UID:-1000}" + GID: "${GID:-1000}" + +name: cleverage-soap-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..c812151 --- /dev/null +++ b/.docker/php/Dockerfile @@ -0,0 +1,30 @@ +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 \ + libxml2-dev \ + && docker-php-ext-configure intl \ + && docker-php-ext-install intl soap opcache \ + && docker-php-ext-enable soap 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..0840f58 --- /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/soap-process-bundle](https://github.com/cleverage/soap-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 ff72e2d..ca08796 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ /composer.lock /vendor +.env +.idea +/phpunit.xml +.phpunit.result.cache +.phpunit.cache +.php-cs-fixer.cache +coverage-report diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..0de9bbb --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,46 @@ +setRules([ + '@PHP71Migration' => true, + '@PHP82Migration' => true, + '@PHPUnit75Migration:risky' => true, + '@Symfony' => true, + '@Symfony:risky' => true, + 'protected_to_private' => false, + 'native_constant_invocation' => ['strict' => false], + 'header_comment' => ['header' => $fileHeaderComment], + 'modernize_strpos' => true, + 'get_class_to_class_keyword' => true, + ]) + ->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..453466d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +v2.0 +------ + +## BC breaks + +* [#4](https://github.com/cleverage/soap-process-bundle/issues/4) 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_soap_process` + + +### Changes + +* [#2](https://github.com/cleverage/soap-process-bundle/issues/2) Add Makefile & .docker for local standalone usage +* [#2](https://github.com/cleverage/soap-process-bundle/issues/2) Add rector, phpstan & php-cs-fixer configurations & apply it +* [#3](https://github.com/cleverage/soap-process-bundle/issues/3) Remove `sidus/base-bundle` dependency + +### Fixes + +v1.0.1 +------ + +### Changes + +* Fixed dependencies after removing sidus/base-bundle from the base process bundle + +v1.0.0 +------ + +* Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9fb4f0a --- /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/soap-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/DependencyInjection/CleverAgeSoapProcessExtension.php b/DependencyInjection/CleverAgeSoapProcessExtension.php deleted file mode 100644 index bdd54e9..0000000 --- a/DependencyInjection/CleverAgeSoapProcessExtension.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @author Vincent Chalnot - * @author Madeline Veyrenc - */ -class CleverAgeSoapProcessExtension extends SidusBaseExtension -{ -} diff --git a/LICENSE b/LICENSE index fdc6131..045d824 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2019 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 new file mode 100644 index 0000000..0a58e32 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +.ONESHELL: +SHELL := /bin/bash + +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 7f8bd11..b9f3384 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,22 @@ CleverAge/SoapProcessBundle ======================= -See process bundle documentation +This bundle is a part of the [CleverAge/ProcessBundle](https://github.com/cleverage/process-bundle) project. +It provides [Soap](https://fr.wikipedia.org/wiki/SOAP) integration on Process bundle. + +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/rest-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/Resources/config/services/registry.yml b/Resources/config/services/registry.yml deleted file mode 100644 index ad1a6cf..0000000 --- a/Resources/config/services/registry.yml +++ /dev/null @@ -1,3 +0,0 @@ -services: - CleverAge\SoapProcessBundle\Registry\ClientRegistry: - public: false diff --git a/Resources/config/services/task.yml b/Resources/config/services/task.yml deleted file mode 100644 index 559188f..0000000 --- a/Resources/config/services/task.yml +++ /dev/null @@ -1,8 +0,0 @@ -services: - CleverAge\SoapProcessBundle\Task\: - resource: '../../../Task/*' - autowire: true - public: true - shared: false - tags: - - { name: monolog.logger, channel: cleverage_process_task } diff --git a/Resources/config/services/transformer.yml b/Resources/config/services/transformer.yml deleted file mode 100644 index 1b82991..0000000 --- a/Resources/config/services/transformer.yml +++ /dev/null @@ -1,8 +0,0 @@ -services: - CleverAge\SoapProcessBundle\Transformer\: - resource: '../../../Transformer/*' - autowire: true - public: false - tags: - - { name: cleverage.transformer } - - { name: monolog.logger, channel: cleverage_process_transformer } diff --git a/composer.json b/composer.json index 2582944..ba7d745 100644 --- a/composer.json +++ b/composer.json @@ -34,15 +34,35 @@ ], "autoload": { "psr-4": { - "CleverAge\\SoapProcessBundle\\": "" + "CleverAge\\SoapProcessBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "CleverAge\\SoapProcessBundle\\Tests\\": "tests/" } }, "require": { - "cleverage/process-bundle": "3.*|dev-v3.0-dev", + "php": ">=8.1", "ext-soap": "*", - "sidus/base-bundle": "~1.0" + "cleverage/process-bundle": "^4.0.0" }, "require-dev": { - "phpunit/phpunit": "~6.4" + "friendsofphp/php-cs-fixer": "*", + "phpstan/extension-installer": "*", + "phpstan/phpstan": "*", + "phpstan/phpstan-symfony": "*", + "phpunit/phpunit": "<10.0", + "rector/rector": "*", + "roave/security-advisories": "dev-latest", + "symfony/test-pack": "^1.1" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "sort-packages": true } } diff --git a/config/services/registry.yaml b/config/services/registry.yaml new file mode 100644 index 0000000..80986ba --- /dev/null +++ b/config/services/registry.yaml @@ -0,0 +1,4 @@ +services: + cleverage_soap_process.registry.client: + class: CleverAge\SoapProcessBundle\Registry\ClientRegistry + public: false diff --git a/config/services/task.yaml b/config/services/task.yaml new file mode 100644 index 0000000..f6f1983 --- /dev/null +++ b/config/services/task.yaml @@ -0,0 +1,12 @@ +services: + cleverage_soap_process.task.request: + class: CleverAge\SoapProcessBundle\Task\RequestTask + public: false + arguments: + - '@monolog.logger' + - '@cleverage_soap_process.registry.client' + tags: + - { name: monolog.logger, channel: cleverage_process_task } + CleverAge\SoapProcessBundle\Task\RequestTask: + alias: cleverage_soap_process.task.request + public: true diff --git a/config/services/transformer.yaml b/config/services/transformer.yaml new file mode 100644 index 0000000..c84d641 --- /dev/null +++ b/config/services/transformer.yaml @@ -0,0 +1,11 @@ +services: + cleverage_soap_process.transformer.request: + class: CleverAge\SoapProcessBundle\Transformer\RequestTransformer + public: false + arguments: + - '@cleverage_soap_process.registry.client' + tags: + - { name: cleverage.transformer } + - { name: monolog.logger, channel: cleverage_process_transformer } + CleverAge\SoapProcessBundle\Transformer\RequestTransformer: + alias: cleverage_soap_process.transformer.request diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..47d52d3 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,27 @@ +## 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/soap-process-bundle +``` + +Remember to add the following line to config/bundles.php (not required if Symfony Flex is used) + +```php +CleverAge\SoapProcessBundle\CleverAgeSoapProcessBundle::class => ['all' => true], +``` + +## Reference + +- Tasks + - [RequestTask](reference/tasks/request_task.md) +- Transformers + - [RequestTransformer] 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/request_task.md b/docs/reference/tasks/request_task.md new file mode 100644 index 0000000..028cf06 --- /dev/null +++ b/docs/reference/tasks/request_task.md @@ -0,0 +1,77 @@ +RequestTask +=============== + +Call a SOAP Request and get result. + +Task reference +-------------- + +* **Client Service Interface**: `CleverAge\SoapProcessBundle\Client\ClientInterface` +* **Task Service**: `CleverAge\SoapProcessBundle\Task\RequestTask` + +Accepted inputs +--------------- + +`array`: list of of the arguments to pass as `$args` to the [SoapClient::__soapCall()](https://www.php.net/manual/en/soapclient.soapcall.php) method. + +Possible outputs +---------------- + +`false|stdClass|array`: the result of the soap call. + +Options +------- + +### For Client + +| Code | Type | Required | Default | Description | +|-----------------|------------------|:---------:|---------|-------------------------------------------------------------------------------| +| `code` | `string` | **X** | | Service identifier, used by Task client option | +| `wsdl` | `string or null` | | | URI of a WSDL file describing the service | +| `options` | `array` | | [] | An associative array specifying additional options for the SOAP client. | +| `options.trace` | `boolean` | | true | Captures request and response information. Add debug informations into logger | + +Calls setter methods in `CleverAge\SoapProcessBundle\Client\ClientInterface` to add more options. + +### For Task + +| Code | Type | Required | Default | Description | +|-------------------------------|-----------------------------------------------|:----------------------------------------:|---------|---------------------------------------------------------------------| +| `client` | `string` | **X** | | `ClientInterface` service identifier | +| `method` | `string` | **X** | | The name of the SOAP function to call. | +| `soap_call_options` | `array or null` | | null | An associative array of options to pass to the client. | +| `soap_call_headers` | `array or null` resolved as \SoapHeader array | | null | An array of headers to be sent along with the SOAP request. | +| `soap_call_headers.namespace` | `array or null` | **X** if `soap_call_headers` is not null | | The namespace of the SOAP header element. | +| `soap_call_headers.data` | `array or null` | **X** if `soap_call_headers` is not null | | A SOAP header's content. It can be a PHP value or a SoapVar object. | + +Examples +-------- + +### Client + +```yaml +services: + app.cleverage_soap_process.client.domain_sample: + class: CleverAge\SoapProcessBundle\Client\Client + bind: + $code: 'domain_sample' + $wsdl: 'https://domain/sample.wsdl' + $options: + trace: true + exceptions: true + calls: + - [ setSoapOptions, [ { features: SOAP_SINGLE_ELEMENT_ARRAYS} ] ] + tags: + - { name: cleverage.soap.client } +``` + +### Task + +```yaml +# Task configuration level +code: + service: '@CleverAge\SoapProcessBundle\Task\RequestTask' + options: + client: domain_sample + method: 'MethodToCall' +``` diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..e9a9e7e --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 10 + paths: + - src + - 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 new file mode 100644 index 0000000..72a2408 --- /dev/null +++ b/rector.php @@ -0,0 +1,30 @@ +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, + SymfonySetList::SYMFONY_64, + SymfonySetList::SYMFONY_71, + SymfonySetList::SYMFONY_CODE_QUALITY, + SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, + SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, + ]) +; diff --git a/CleverAgeSoapProcessBundle.php b/src/CleverAgeSoapProcessBundle.php similarity index 60% rename from CleverAgeSoapProcessBundle.php rename to src/CleverAgeSoapProcessBundle.php index 01fd46d..7a61aa5 100644 --- a/CleverAgeSoapProcessBundle.php +++ b/src/CleverAgeSoapProcessBundle.php @@ -1,8 +1,11 @@ - - * @author Vincent Chalnot - * @author Madeline Veyrenc - */ class CleverAgeSoapProcessBundle extends Bundle { /** - * Adding compiler passes to inject services into registry - * - * @param ContainerBuilder $container + * Adding compiler passes to inject services into registry. */ public function build(ContainerBuilder $container): void { $container->addCompilerPass( new RegistryCompilerPass( - ClientRegistry::class, + 'cleverage_soap_process.registry.client', 'cleverage.soap.client', 'addClient' ) ); } + + public function getPath(): string + { + return \dirname(__DIR__); + } } diff --git a/Client/Client.php b/src/Client/Client.php similarity index 56% rename from Client/Client.php rename to src/Client/Client.php index b70227c..9d98faa 100644 --- a/Client/Client.php +++ b/src/Client/Client.php @@ -1,8 +1,11 @@ - */ class Client implements ClientInterface { - /** @var string */ - private $code; - - /** @var string|null */ - private $wsdl; - - /** @var array */ - private $options; - - /** @var array */ - private $soapOptions; - - /** @var null|\SoapHeader[] */ - private $soapHeaders; + /** @var array|null */ + private ?array $soapOptions = null; - /** @var LoggerInterface */ - private $logger; + /** @var \SoapHeader[]|null */ + private ?array $soapHeaders = null; - /** @var \SoapClient */ - private $soapClient; + private ?\SoapClient $soapClient = null; - /** @var string */ - private $lastRequest; + private ?string $lastRequest = null; - /** @var string */ - private $lastRequestHeaders; + private ?string $lastRequestHeaders = null; - /** @var string */ - private $lastResponse; + private ?string $lastResponse = null; - /** @var string */ - private $lastResponseHeaders; + private ?string $lastResponseHeaders = null; /** * Client constructor. - * - * @param LoggerInterface $logger - * @param string $code - * @param string|null $wsdl - * @param array $options */ - public function __construct(LoggerInterface $logger, string $code, ?string $wsdl, array $options) - { - $this->logger = $logger; - $this->code = $code; - $this->wsdl = $wsdl; - $this->options = $options; + public function __construct( + private readonly LoggerInterface $logger, + private readonly string $code, + private ?string $wsdl, + /** @var array */ + private array $options = [], + ) { } - /** - * @return LoggerInterface - */ public function getLogger(): LoggerInterface { return $this->logger; } /** - * {@inheritdoc} * @throws \UnexpectedValueException */ public function getCode(): string { - if (!$this->code) { + if ('' === $this->code || '0' === $this->code) { throw new \UnexpectedValueException('Client code is not defined'); } return $this->code; } - /** - * {@inheritdoc} - */ public function getWsdl(): ?string { return $this->wsdl; } - /** - * {@inheritdoc} - */ public function setWsdl(?string $wsdl): void { $this->wsdl = $wsdl; } - /** - * {@inheritdoc} - */ public function getOptions(): array { return $this->options; } - /** - * {@inheritdoc} - */ public function setOptions(array $options): void { $this->options = $options; } - /** - * @return array|null - */ public function getSoapOptions(): ?array { return $this->soapOptions; } - /** - * @param array|null $soapOptions - */ - public function setSoapOptions(array $soapOptions = null): void + public function setSoapOptions(?array $soapOptions = null): void { $this->soapOptions = $soapOptions; } @@ -148,127 +108,93 @@ public function getSoapHeaders(): ?array /** * @param \SoapHeader[]|null $soapHeaders */ - public function setSoapHeaders(array $soapHeaders = null): void + public function setSoapHeaders(?array $soapHeaders = null): void { $this->soapHeaders = $soapHeaders; } - /** - * @return \SoapClient|null - */ public function getSoapClient(): ?\SoapClient { return $this->soapClient; } - /** - * @param \SoapClient $soapClient - */ public function setSoapClient(\SoapClient $soapClient): void { $this->soapClient = $soapClient; } - /** - * @return string - */ public function getLastRequest(): ?string { return $this->lastRequest; } - /** - * @param string $lastRequest - */ public function setLastRequest(?string $lastRequest): void { $this->lastRequest = $lastRequest; } - /** - * @return string - */ public function getLastRequestHeaders(): ?string { return $this->lastRequestHeaders; } - /** - * @param string $lastRequestHeaders - */ public function setLastRequestHeaders(?string $lastRequestHeaders): void { $this->lastRequestHeaders = $lastRequestHeaders; } - /** - * @return string - */ public function getLastResponse(): ?string { return $this->lastResponse; } - /** - * @param string $lastResponse - */ public function setLastResponse(?string $lastResponse): void { $this->lastResponse = $lastResponse; } - /** - * @return string - */ public function getLastResponseHeaders(): ?string { return $this->lastResponseHeaders; } - /** - * @param string $lastResponseHeaders - */ public function setLastResponseHeaders(?string $lastResponseHeaders): void { $this->lastResponseHeaders = $lastResponseHeaders; } - /** - * {@inheritdoc} - */ - public function call(string $method, array $input = []) + public function call(string $method, array $input = []): mixed { $this->initializeSoapClient(); - $callMethod = sprintf('soapCall%s', ucfirst($method)); + $callMethod = \sprintf('soapCall%s', ucfirst($method)); if (method_exists($this, $callMethod)) { return $this->$callMethod($input); } $this->getLogger()->notice( - sprintf("Soap call '%s' on '%s'", $method, $this->getWsdl()) + \sprintf("Soap call '%s' on '%s'", $method, $this->getWsdl()) ); return $this->doSoapCall($method, $input); } /** - * @param string $method - * @param array $input + * @param array $input * * @return bool|mixed */ - protected function doSoapCall(string $method, array $input = []) + protected function doSoapCall(string $method, array $input = []): mixed { - if (!$this->getSoapClient()) { + if (!$this->getSoapClient() instanceof \SoapClient) { throw new \InvalidArgumentException('Soap client is not initialized'); } try { $result = $this->getSoapClient()->__soapCall($method, $input, $this->getSoapOptions(), $this->getSoapHeaders()); - } /** @noinspection PhpRedundantCatchClauseInspection */ catch (\SoapFault $e) { + } catch (\SoapFault $e) { $this->getLastRequestTrace(); $this->getLogger()->alert( - sprintf("Soap call '%s' on '%s' failed : %s", $method, $this->getWsdl(), $e->getMessage()), + \sprintf("Soap call '%s' on '%s' failed : %s", $method, $this->getWsdl(), $e->getMessage()), $this->getLastRequestTraceArray() ); @@ -277,9 +203,9 @@ protected function doSoapCall(string $method, array $input = []) $this->getLastRequestTrace(); - if (array_key_exists('trace', $this->getOptions()) && $this->getOptions()['trace']) { + if (\array_key_exists('trace', $this->getOptions()) && $this->getOptions()['trace']) { $this->getLogger()->debug( - sprintf("Trace of soap call '%s' on '%s'", $method, $this->getWsdl()), + \sprintf("Trace of soap call '%s' on '%s'", $method, $this->getWsdl()), $this->getLastRequestTraceArray() ); } @@ -288,32 +214,34 @@ protected function doSoapCall(string $method, array $input = []) } /** - * Initialize \SoapClient object - * - * @return void + * Initialize \SoapClient object. */ protected function initializeSoapClient(): void { - if (!$this->getSoapClient()) { + if (!$this->getSoapClient() instanceof \SoapClient) { $options = array_merge($this->getOptions(), ['trace' => true]); $this->setSoapClient(new \SoapClient($this->getWsdl(), $options)); } } - /** - */ protected function getLastRequestTrace(): void { - if ($this->getSoapClient()) { - $this->setLastRequest($this->getSoapClient()->__getLastRequest()); - $this->setLastRequestHeaders($this->getSoapClient()->__getLastRequestHeaders()); - $this->setLastResponse($this->getSoapClient()->__getLastResponse()); - $this->setLastResponseHeaders($this->getSoapClient()->__getLastResponseHeaders()); + $soapClient = $this->getSoapClient(); + if ($soapClient instanceof \SoapClient) { + $this->setLastRequest($soapClient->__getLastRequest()); + $this->setLastRequestHeaders($soapClient->__getLastRequestHeaders()); + $this->setLastResponse($soapClient->__getLastResponse()); + $this->setLastResponseHeaders($soapClient->__getLastResponseHeaders()); } } /** - * @return array + * @return array{ + * 'LastRequest': ?string, + * 'LastRequestHeaders': ?string, + * 'LastResponse': ?string, + * 'LastResponseHeaders': ?string + * } */ protected function getLastRequestTraceArray(): array { diff --git a/Client/ClientInterface.php b/src/Client/ClientInterface.php similarity index 58% rename from Client/ClientInterface.php rename to src/Client/ClientInterface.php index e584068..10fbd55 100644 --- a/Client/ClientInterface.php +++ b/src/Client/ClientInterface.php @@ -1,8 +1,11 @@ - - */ interface ClientInterface { /** * Return the code of the client used in client registry. - * - * @return string */ public function getCode(): string; /** * Return the URI of the WSDL file or NULL if working in non-WSDL mode. - * - * @return string */ public function getWsdl(): ?string; /** * Set the URI of the WSDL file or NULL if working in non-WSDL mode. - * - * @param string $wsdl - * - * @return void */ public function setWsdl(?string $wsdl): void; /** - * Return the Soap client options + * Return the Soap client options. * * @see http://php.net/manual/en/soapclient.soapclient.php * - * @return array + * @return array */ public function getOptions(): array; /** - * Set the Soap client options + * Set the Soap client options. * * @see http://php.net/manual/en/soapclient.soapclient.php * - * @param array $options - * - * @return void + * @param array $options */ public function setOptions(array $options): void; /** - * Return the Soap call options + * Return the Soap call options. * * @see https://www.php.net/manual/en/soapclient.soapcall.php * - * @return array|null + * @return array|null */ public function getSoapOptions(): ?array; /** - * Set the Soap call options + * Set the Soap call options. * * @see https://www.php.net/manual/en/soapclient.soapcall.php * - * @param array|null $options - * - * @return void + * @param array|null $options */ - public function setSoapOptions(array $options = null): void; + public function setSoapOptions(?array $options = null): void; /** - * Return the Soap call headers + * Return the Soap call headers. * * @see https://www.php.net/manual/en/soapclient.soapcall.php * @@ -90,43 +76,28 @@ public function setSoapOptions(array $options = null): void; public function getSoapHeaders(): ?array; /** - * Set the Soap call headers + * Set the Soap call headers. * * @see https://www.php.net/manual/en/soapclient.soapcall.php * * @param \SoapHeader[]|null $headers - * - * @return void */ - public function setSoapHeaders(array $headers = null): void; + public function setSoapHeaders(?array $headers = null): void; - /** - * @return string - */ public function getLastRequest(): ?string; - /** - * @return string - */ public function getLastRequestHeaders(): ?string; - /** - * @return string - */ public function getLastResponse(): ?string; - /** - * @return string - */ public function getLastResponseHeaders(): ?string; /** - * Call Soap method + * Call Soap method. * - * @param string $method - * @param array $input + * @param array $input * - * @return mixed + * @return bool|mixed */ - public function call(string $method, array $input = []); + public function call(string $method, array $input = []): mixed; } diff --git a/src/DependencyInjection/CleverAgeSoapProcessExtension.php b/src/DependencyInjection/CleverAgeSoapProcessExtension.php new file mode 100644 index 0000000..2114981 --- /dev/null +++ b/src/DependencyInjection/CleverAgeSoapProcessExtension.php @@ -0,0 +1,47 @@ +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/Exception/MissingClientException.php b/src/Exception/MissingClientException.php similarity index 71% rename from Exception/MissingClientException.php rename to src/Exception/MissingClientException.php index e90b4e8..cb7ab65 100644 --- a/Exception/MissingClientException.php +++ b/src/Exception/MissingClientException.php @@ -1,8 +1,11 @@ - */ @@ -21,8 +24,6 @@ class MissingClientException extends \UnexpectedValueException implements Proces { /** * @param string $code - * - * @return MissingClientException */ public static function create($code): self { diff --git a/Registry/ClientRegistry.php b/src/Registry/ClientRegistry.php similarity index 62% rename from Registry/ClientRegistry.php rename to src/Registry/ClientRegistry.php index c7a92e0..479da20 100644 --- a/Registry/ClientRegistry.php +++ b/src/Registry/ClientRegistry.php @@ -1,8 +1,11 @@ - */ class ClientRegistry { /** @var ClientInterface[] */ - private $clients = []; + private array $clients = []; - /** - * @param ClientInterface $client - */ public function addClient(ClientInterface $client): void { - if (array_key_exists($client->getCode(), $this->getClients())) { + if (\array_key_exists($client->getCode(), $this->getClients())) { throw new \UnexpectedValueException("Client {$client->getCode()} is already defined"); } $this->clients[$client->getCode()] = $client; @@ -43,13 +43,9 @@ public function getClients(): array } /** - * @param string $code - * * @throws MissingClientException - * - * @return ClientInterface */ - public function getClient($code): ClientInterface + public function getClient(string $code): ClientInterface { if (!$this->hasClient($code)) { throw MissingClientException::create($code); @@ -58,13 +54,8 @@ public function getClient($code): ClientInterface return $this->getClients()[$code]; } - /** - * @param string $code - * - * @return bool - */ - public function hasClient($code): bool + public function hasClient(string $code): bool { - return array_key_exists($code, $this->getClients()); + return \array_key_exists($code, $this->getClients()); } } diff --git a/Task/RequestTask.php b/src/Task/RequestTask.php similarity index 69% rename from Task/RequestTask.php rename to src/Task/RequestTask.php index 8e0200e..428e358 100644 --- a/Task/RequestTask.php +++ b/src/Task/RequestTask.php @@ -1,8 +1,11 @@ - + * @phpstan-type RequestOptions array{ + * 'client': string, + * 'method': string, + * 'soap_call_options': array|null, + * 'soap_call_headers': array<\SoapHeader>|null, + * } */ class RequestTask extends AbstractConfigurableTask { - - /** @var LoggerInterface */ - protected $logger; - - /** @var ClientRegistry */ - protected $registry; - - /** - * SoapClientTask constructor. - * - * @param LoggerInterface $logger - * @param ClientRegistry $registry - */ - public function __construct(LoggerInterface $logger, ClientRegistry $registry) + public function __construct(protected LoggerInterface $logger, protected ClientRegistry $registry) { - $this->logger = $logger; - $this->registry = $registry; } - /** - * {@inheritdoc} - */ public function execute(ProcessState $state): void { + /** @var RequestOptions $options */ $options = $this->getOptions($state); $client = $this->registry->getClient($options['client']); + /** @var array $input */ $input = $state->getInput() ?: []; - $client->setSoapOptions($this->getOption($state, 'soap_call_options')); - $client->setSoapHeaders($this->getOption($state, 'soap_call_headers')); + /** @var array|null $soapCallOptions */ + $soapCallOptions = $this->getOption($state, 'soap_call_options'); + $client->setSoapOptions($soapCallOptions); + /** @var array<\SoapHeader>|null $soapCallHeaders */ + $soapCallHeaders = $this->getOption($state, 'soap_call_headers'); + $client->setSoapHeaders($soapCallHeaders); $result = $client->call($options['method'], $input); @@ -74,9 +68,9 @@ public function execute(ProcessState $state): void $this->logger->error('Empty resultset for query', $logContext); - if ($state->getTaskConfiguration()->getErrorStrategy() === TaskConfiguration::STRATEGY_SKIP) { + if (TaskConfiguration::STRATEGY_SKIP === $state->getTaskConfiguration()->getErrorStrategy()) { $state->setSkipped(true); - } elseif ($state->getTaskConfiguration()->getErrorStrategy() === TaskConfiguration::STRATEGY_STOP) { + } elseif (TaskConfiguration::STRATEGY_STOP === $state->getTaskConfiguration()->getErrorStrategy()) { $state->setStopped(true); } } @@ -84,9 +78,6 @@ public function execute(ProcessState $state): void $state->setOutput($result); } - /** - * {@inheritdoc} - */ protected function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired( @@ -107,7 +98,7 @@ protected function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('soap_call_headers', ['array', 'null']); $resolver->setNormalizer('soap_call_headers', function (Options $options, $headers) { - if ($headers === null) { + if (null === $headers) { return null; } @@ -115,7 +106,9 @@ protected function configureOptions(OptionsResolver $resolver): void $this->configureSoapCallHeaderOption($headerResolver); $resolvedHeaders = []; + /** @var array> $headers */ foreach ($headers as $name => $header) { + /** @var array{'namespace': string, 'data': array} $resolvedHeader */ $resolvedHeader = $headerResolver->resolve($header); $resolvedHeaders[] = new \SoapHeader($resolvedHeader['namespace'], $name, $resolvedHeader['data']); } @@ -124,7 +117,7 @@ protected function configureOptions(OptionsResolver $resolver): void }); } - protected function configureSoapCallHeaderOption(OptionsResolver $resolver) + protected function configureSoapCallHeaderOption(OptionsResolver $resolver): void { $resolver->setRequired('namespace'); $resolver->setRequired('data'); diff --git a/Transformer/RequestTransformer.php b/src/Transformer/RequestTransformer.php similarity index 63% rename from Transformer/RequestTransformer.php rename to src/Transformer/RequestTransformer.php index f66e067..3e3c2d5 100644 --- a/Transformer/RequestTransformer.php +++ b/src/Transformer/RequestTransformer.php @@ -1,8 +1,11 @@ - + * @phpstan-type TransformerOptions array{ + * 'client': string, + * 'method': string, + * } */ class RequestTransformer implements ConfigurableTransformerInterface { - /** @var ClientRegistry */ - protected $registry; - - /** - * RequestTransformer constructor. - * - * @param ClientRegistry $registry - */ - public function __construct(ClientRegistry $registry) + public function __construct(protected ClientRegistry $registry) { - $this->registry = $registry; } - /** - * {@inheritdoc} + * @param array $options */ - public function transform($value, array $options = []) + public function transform(mixed $value, array $options = []): mixed { + if (!\is_array($value)) { + throw new \UnexpectedValueException('Expecting an array of value'); + } + $resolver = new OptionsResolver(); $this->configureOptions($resolver); + /** @var TransformerOptions $options */ $options = $resolver->resolve($options); $client = $this->registry->getClient($options['client']); @@ -50,18 +49,13 @@ public function transform($value, array $options = []) } /** - * Returns the unique code to identify the transformer - * - * @return string + * Returns the unique code to identify the transformer. */ public function getCode(): string { return 'soap_request'; } - /** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired( diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29