diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dd7983d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml,neon}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..622c1b9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +* text=auto eol=lf +*.pp eol=lf linguist-language=EBNF +*.pp2 eol=lf linguist-language=EBNF + +.editorconfig export-ignore +.php-cs-fixer.php export-ignore +.gitattributes export-ignore +.gitignore export-ignore + +phpunit.xml export-ignore +psalm.xml export-ignore +rector.php export-ignore + +/.github export-ignore +/tests export-ignore diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml new file mode 100644 index 0000000..db97a49 --- /dev/null +++ b/.github/workflows/codestyle.yml @@ -0,0 +1,45 @@ +name: codestyle + +on: + push: + pull_request: + +jobs: + psalm: + name: Code Style + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php: [ '8.3' ] + os: [ ubuntu-latest ] + steps: + - name: Set Git To Use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Validate Composer + run: composer validate + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Restore Composer Cache + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- + - name: Install Dependencies + uses: nick-invision/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-dist --no-interaction --no-progress + - name: Check Code Style + run: composer phpcs:check diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..3019d6c --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,49 @@ +name: security + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + security: + name: Security + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php: [ '8.3' ] + os: [ ubuntu-latest ] + steps: + - name: Set Git To Use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Validate Composer + run: composer validate + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Restore Composer Cache + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- + - name: Install Dependencies + uses: nick-invision/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-dist --no-interaction --no-progress + - name: Composer Audit + run: composer audit + - name: Security Advisories + run: composer require --dev roave/security-advisories:dev-latest diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..3d93fd2 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,45 @@ +name: static-analysis + +on: + push: + pull_request: + +jobs: + psalm: + name: Psalm + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php: [ '8.3' ] + os: [ ubuntu-latest ] + steps: + - name: Set Git To Use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Validate Composer + run: composer validate + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Restore Composer Cache + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- + - name: Install Dependencies + uses: nick-invision/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-dist --no-interaction --no-progress + - name: Static Analysis + run: composer linter:check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..d263d01 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,52 @@ +name: tests + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + tests: + name: Tests (${{matrix.php}}, ${{ matrix.os }}, ${{ matrix.stability }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php: [ '8.3' ] + os: [ ubuntu-latest, macos-latest, windows-latest ] + stability: [ prefer-lowest, prefer-stable ] + steps: + - name: Set Git To Use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: pecl + ini-values: "memory_limit=-1" + - name: Validate Composer + run: composer validate + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Restore Composer Cache + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- + - name: Install Dependencies + uses: nick-invision/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --${{ matrix.stability }} --ignore-platform-reqs --prefer-dist --no-interaction --no-progress + - name: Execute Unit Tests + run: composer test:unit + - name: Execute Functional Tests + run: composer test:functional diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5fffcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# IDE +/.idea/ + +# Composer +/composer.lock +/vendor/ + +# Testing +test.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..6061ed1 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,16 @@ +in([__DIR__ . '/src']); + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PER-CS2.0' => true, + '@PER-CS2.0:risky' => true, + 'strict_param' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + ]) + ->setCacheFile(__DIR__ . '/vendor/.cache.php-cs-fixer') + ->setFinder($files); diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4602ec1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Nesmeyanov Kirill + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbeaaca --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +
+ + + + +# HTTP Factory + +A set of drivers for encoding HTTP responses and decoding HTTP requests. + +## Installation + +PewPew HTTP Factory is available as Composer repository and can be installed +using the following command in a root of your project: + +```bash +$ composer require pew-pew/http-factory +``` + +More detailed installation [instructions are here](https://getcomposer.org/doc/01-basic-usage.md). + +## Usage + +### Decoder + +```php +// Symfony Request +$request = new \Symfony\Component\HttpFoundation\Request(); + +// Requests Factory +$requests = new \PewPew\HttpFactory\RequestDecoderFactory([ + new \PewPew\HttpFactory\Driver\JsonDriver(), +]); + +$payload = $requests + ->createDecoder($request) // Detect passed "content-type" header and + // create decoder if available. + ?->decode($request->getContent(true)); // Decode request body. +``` + +### Encoder + +```php +// Symfony Request +$request = new \Symfony\Component\HttpFoundation\Request(); + +// Responses Factory +$responses = new \PewPew\HttpFactory\ResponseEncoderFactory([ + new \PewPew\HttpFactory\Driver\JsonDriver(), +]); + +$response = $responses + ->createEncoder($request) // Detect passed "accept" header and create + // encoder if available. + ?->encode(['some' => 'any'], 200); // Encode payload and create response. +``` + +### Symfony Integration + +Add the bundle to your `bundles.php` file: + +```php +// bundles.php +return [ + // ... + PewPew\HttpFactory\HttpFactoryBundle::class => ['all' => true], +]; +``` + +Use `ResponseEncoderFactoryInterface` and `RequestDecoderFactoryInterface` +in your services: + +```php +use PewPew\HttpFactory\ResponseEncoderFactoryInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; + +final readonly class ExampleController +{ + public function __construct( + private ResponseEncoderFactoryInterface $responses, + ) {} + + public function someAction(Request $request): Response + { + $encoder = $this->responses->createEncoder($request); + + if ($encoder === null) { + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException( + 'Unsupported "accept" request header', + ); + } + + return $encoder->encode([ + 'status' => 'ok' + ], Response::HTTP_OK); + } +} +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b72df11 --- /dev/null +++ b/composer.json @@ -0,0 +1,60 @@ +{ + "name": "pew-pew/http-factory", + "license": "MIT", + "description": "HTTP factory for decoding request and encoding responses with symfony integration", + "type": "library", + "require": { + "php": "^8.3", + "symfony/http-foundation": "^5.4|^6.0|^7.0" + }, + "autoload": { + "psr-4": { + "PewPew\\HttpFactory\\": "src" + } + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.49", + "phpunit/phpunit": "^10.5", + "symfony/var-dumper": "^5.4|^6.0|^7.0", + "vimeo/psalm": "^5.21" + }, + "autoload-dev": { + "psr-4": { + "PewPew\\HttpFactory\\Tests\\": "tests" + } + }, + "suggest": { + "ext-json": "Adds JSON decoder and encoder", + "symfony/yaml": "Adds YAML decoder and encoder", + "rybakit/msgpack": "Adds MSGPACK decoder and encoder", + "symfony/http-kernel": "Adds Symfony Bundle support (PewPew\\HttpFactory\\HttpFactoryBundle)" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-main": "1.0.x-dev" + } + }, + "config": { + "sort-packages": true, + "platform-check": true, + "bin-compat": "full", + "optimize-autoloader": true, + "preferred-install": { + "*": "dist" + } + }, + "scripts": { + "test": ["@test:unit", "@test:functional"], + "test:unit": "phpunit --testdox", + "test:functional": "phpunit --testdox --testsuite=functional", + "linter": "@linter:check", + "linter:check": "psalm --no-cache", + "linter:fix": "psalm --no-cache --alter", + "phpcs": "@phpcs:check", + "phpcs:check": "php-cs-fixer fix --config=.php-cs-fixer.php --allow-risky=yes --dry-run --verbose --diff", + "phpcs:fix": "php-cs-fixer fix --config=.php-cs-fixer.php --allow-risky=yes --verbose --diff" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..ea677ba --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,33 @@ + +