diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1754c28 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +/.github export-ignore +/build export-ignore +/tests export-ignore +/tests-resources export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore + +*.php diff=php \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..9385edd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,56 @@ +name: Tests + +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master + +jobs: + tests: + name: PHP ${{ matrix.php }} + runs-on: ubuntu-latest + strategy: + matrix: + php: + - 7.0 + - 7.1 + - 7.2 + - 7.3 + - 7.4 + - 8.0 + - 8.1 + - 8.2 + - 8.3 + - 8.4 + - 8.5 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + + - id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('composer.json') }} + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction + + - name: Audit dependencies + run: composer audit || true + + - name: Running unit tests + run: php vendor/bin/phpunit --configuration tests-resources/phpunit.dist.xml --testsuite unit diff --git a/.gitignore b/.gitignore index 2a0d621..130e21e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /tests-resources/config/config.yml +/tests-resources/phpunit.xml /vendor /composer.lock -/phpunit.xml -/composer.phar +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6da4a7b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: php -php: -- '7.0' -- '7.1' -- '7.2' -- '7.3' -- '7.4' -- '8.0' -before_script: -- composer self-update -- composer install -script: ./vendor/bin/phpunit --configuration phpunit.dist.xml --testsuite unit diff --git a/CHANGELOG.md b/CHANGELOG.md index c67ebb6..190eb08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,63 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [3.0.13] - 2026-01-12 +### Added +- PHP-8.5 support + +## [3.0.12] - 2025-07-03 +### Added +- PHP-8.4 support + +## [3.0.11] - 2024-03-27 +### Added +- service url scheme and host validation +- service url with port support + +### Changed +- PSR-18 HTTP client related exceptions namespace moved + +## [3.0.10] - 2024-01-16 +### Added +- PHP-8.3 support +- `SendSmsBag::$timeRestriction` optional parameter +- `SendSmssBag::$timeRestriction` optional parameter +- `SendSmsToGroupBag::$timeRestriction` optional parameter +- `ScheduleSmsBag::$timeRestriction` optional parameter +- `ScheduleSmssBag::$timeRestriction` optional parameter +- `ScheduleSmsToGroupBag::$timeRestriction` optional parameter +### Fixed +- sending/scheduling smses in large amount + +## [3.0.9] - 2024-01-09 +### Fixed +- expired MFA code verification + +## [3.0.8] - 2023-10-18 +### Added +- `psr/http-message` v2 support + +## [3.0.7] - 2022-11-21 +### Fixed +- dynamic property deprecations + +## [3.0.6] - 2022-03-28 +### Fixed +- HTTP headers parsing, PSR-7 compliant + +## [3.0.5] - 2022-03-23 +### Fixed +- HTTP headers parsing, `HttpClient` issue + +## [3.0.4] - 2022-01-17 +### Added +- `psr/log` v2, v3 support + +## [3.0.3] - 2021-07-21 +### Fixed +- Guzzle PSR7 incompatible URI paths, `The path of a URI with an authority must start with a slash "/" or be empty` + +## [3.0.2] - 2021-01-29 ### Added - PHP-8 support diff --git a/Makefile b/Makefile deleted file mode 100644 index 199e534..0000000 --- a/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.PHONY: help - -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.DEFAULT_GOAL := help - -prepare: ## load dependencies - docker-compose run -T php /usr/bin/composer update - -test: prepare ## run test - docker-compose run -T php php vendor/bin/phpunit --configuration phpunit.xml - -test-suite: prepare ## run test against suite, ex: make test-suite SUITE="unit" - docker-compose run -T php php vendor/bin/phpunit --configuration phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/README.md b/README.md index 4f144c0..95ef8f3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SMSAPI PHP Client -[![Build Status](https://travis-ci.org/smsapi/smsapi-php-client.svg?branch=master)](https://travis-ci.org/smsapi/smsapi-php-client) +[![Build Status](https://github.com/smsapi/smsapi-php-client/actions/workflows/tests.yml/badge.svg)](https://github.com/smsapi/smsapi-php-client/actions/workflows/tests.yml) [![Packagist - latest version](https://img.shields.io/packagist/v/smsapi/php-client.svg)](https://packagist.org/packages/smsapi/php-client) [![Packagist - downloads](https://img.shields.io/packagist/dt/smsapi/php-client.svg)](https://packagist.org/packages/smsapi/php-client) [![Packagist - license](https://img.shields.io/packagist/l/smsapi/php-client.svg)](https://packagist.org/packages/smsapi/php-client) @@ -118,7 +118,7 @@ $apiToken = '0000000000000000000000000000000000000000'; $service = $client->smsapiPlService($apiToken); ``` -## How to use a service with custom URI? +### How to use *SMSAPI.SE* or *SMSAPI.BG* services? ```php smsapiComServiceWithUri($apiToken, $uri); ``` @@ -185,50 +185,59 @@ $service->smsFeature() ->sendSms($sms); ``` -### How to send a SMS with optional from field? +### How to use request parameters? + +Request parameters are represented in a form of data transfer object. +DTOs can be found by searching for 'bag' postfixed classes. +Each bag may contain required and optional parameters. + +#### Required parameters + +Required parameters are that class public properties, usually accessible via some form of a setter or named constructor. +Each parameter can be also set directly by setting bag property, as in example: + +##### How to change SMS encoding? ```php from = 'Test'; +$sms->encoding = 'utf-8'; -$service->smsFeature() - ->sendSms($sms); ``` -For more usage examples take a look at client test suite. +#### Optional parameters -### How to use optional request parameters? +Some of request's optional parameters have been described by docblock's '@property' annotation. +You can always add any not documented here optional parameter by setting dynamic property to 'bag'. +Camel case property names are being converted to snake case on the fly. -Request parameters are represented in a form of data transfer object. -DTOs can be found by searching for 'bag' postfixed classes. -Each bag may contain required and optional parameters. -Required parameters are that class public properties, usually accessible via some form of a setter or named constructor. -Optional parameters are described by docblock's '@property' annotation. - -Each parameter can be also set directly by setting bag property, as in example: +##### How to send a SMS with optional from field? ```php encoding = 'utf-8'; +$sms->from = 'Test'; +$service->smsFeature() + ->sendSms($sms); ``` +For more usage examples take a look at client test suite. + ## How to use additional features? ### How to use proxy server? @@ -268,14 +277,51 @@ $logger = new class() implements LoggerInterface $client->setLogger($logger); ``` -## Test package -1. Download package: `composer create-project smsapi/php-client` -2. Execute tests: `./vendor/bin/phpunit --configuration phpunit.dist.xml` +## How to test package + +Copy `phpunit.dist.xml` to `phpunit.xml`. You may adjust it to your needs then. + +Copy `tests-resources/config/config.dist.yml` to `tests-resources/config/config.yml`. Fill in SMSAPI service connection data. + +### How to run unit tests + +Unit tests are included into package build process and run against its current version on every commit (see workflow Tests). +You can run those tests locally with ease using provided Docker configuration, simply run: + +```shell +make test-suite SUITE="unit" +``` + +### How to run integration tests + +Note that integration test works within an account you have configured in `tests-resources/config/config.yml`. +Although those test have been written to self-cleanup on exit, in case of failure some trash data may stay. +Use it with caution. + +```shell +make test-suite SUITE="integration" +``` + +### How to run feature tests + +Feature test groups are defined in `phpunit.dist.xml`. To run tests execute: + +```shell +make test-suite SUITE="feature-contacts" +``` + +### How to run tests against PHP8 + +To run any of mentioned above against PHP8 use make targets with `php8` suffix. See `Makefile.php8`. ## Docs & Infos * [SMSAPI.COM API documentation](https://www.smsapi.com/docs) * [SMSAPI.PL API documentation](https://www.smsapi.pl/docs) -* [Repository on GitHub](https://github.com/smsapi/smsapi-php-client) -* [Package on Packagist](https://packagist.org/packages/smsapi/php-client) +* [SMSAPI.SE API documentation](https://www.smsapi.se/docs) +* [SMSAPI.BG API documentation](https://www.smsapi.bg/docs) * [SMSAPI.COM web page](https://smsapi.com) * [SMSAPI.PL web page](https://smsapi.pl) +* [SMSAPI.SE web page](https://smsapi.se) +* [SMSAPI.BG web page](https://smsapi.bg) +* [Repository on GitHub](https://github.com/smsapi/smsapi-php-client) +* [Package on Packagist](https://packagist.org/packages/smsapi/php-client) diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 0000000..5d807b1 --- /dev/null +++ b/build/Makefile @@ -0,0 +1,46 @@ +.PHONY: help build test + +include $(CURDIR)/php-7.0/Makefile +include $(CURDIR)/php-7.1/Makefile +include $(CURDIR)/php-7.2/Makefile +include $(CURDIR)/php-7.3/Makefile +include $(CURDIR)/php-7.4/Makefile +include $(CURDIR)/php-8.0/Makefile +include $(CURDIR)/php-8.1/Makefile +include $(CURDIR)/php-8.2/Makefile +include $(CURDIR)/php-8.3/Makefile +include $(CURDIR)/php-8.4/Makefile + +help: + @grep -hE '^[a-zA-Z0-9_.-]+:.*?## .*$$' $(MAKEFILE_LIST) \ + | sort \ + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +build: ## build php images + docker compose build + +test: ## test all against all php images + $(MAKE) test-php-7.0 + $(MAKE) test-php-7.1 + $(MAKE) test-php-7.2 + $(MAKE) test-php-7.3 + $(MAKE) test-php-7.4 + $(MAKE) test-php-8.0 + $(MAKE) test-php-8.1 + $(MAKE) test-php-8.2 + $(MAKE) test-php-8.3 + $(MAKE) test-php-8.4 + +test-unit: ## test unit suite against all php images + $(MAKE) test-suite-php-7.0 SUITE=unit + $(MAKE) test-suite-php-7.1 SUITE=unit + $(MAKE) test-suite-php-7.2 SUITE=unit + $(MAKE) test-suite-php-7.3 SUITE=unit + $(MAKE) test-suite-php-7.4 SUITE=unit + $(MAKE) test-suite-php-8.0 SUITE=unit + $(MAKE) test-suite-php-8.1 SUITE=unit + $(MAKE) test-suite-php-8.2 SUITE=unit + $(MAKE) test-suite-php-8.3 SUITE=unit + $(MAKE) test-suite-php-8.4 SUITE=unit + +.DEFAULT_GOAL := help diff --git a/build/docker-compose.yml b/build/docker-compose.yml new file mode 100644 index 0000000..dcbfba0 --- /dev/null +++ b/build/docker-compose.yml @@ -0,0 +1,80 @@ +version: '3.5' + +services: + + php-7.0: + build: + context: php-7.0 + volumes: + - ..:/app + network_mode: host + + php-7.1: + build: + context: php-7.1 + volumes: + - ..:/app + network_mode: host + + php-7.2: + build: + context: php-7.2 + volumes: + - ..:/app + network_mode: host + + php-7.3: + build: + context: php-7.3 + volumes: + - ..:/app + network_mode: host + + php-7.4: + build: + context: php-7.4 + volumes: + - ..:/app + network_mode: host + + php-8.0: + build: + context: php-8.0 + volumes: + - ..:/app + network_mode: host + + php-8.1: + build: + context: php-8.1 + volumes: + - ..:/app + network_mode: host + + php-8.2: + build: + context: php-8.2 + volumes: + - ..:/app + network_mode: host + + php-8.3: + build: + context: php-8.3 + volumes: + - ..:/app + network_mode: host + + php-8.4: + build: + context: php-8.4 + volumes: + - ..:/app + network_mode: host + + php-8.5: + build: + context: php-8.5 + volumes: + - ..:/app + network_mode: host \ No newline at end of file diff --git a/Dockerfile b/build/php-7.0/Dockerfile similarity index 62% rename from Dockerfile rename to build/php-7.0/Dockerfile index f1bf11c..ed516f7 100644 --- a/Dockerfile +++ b/build/php-7.0/Dockerfile @@ -1,4 +1,4 @@ -FROM composer:latest as composer +FROM composer:2.2 as composer FROM php:7.0-cli-alpine RUN apk update && \ @@ -8,10 +8,6 @@ RUN apk update && \ g++ \ make -RUN pecl install xdebug-2.7.2 && \ - pecl clear-cache && \ - docker-php-ext-enable xdebug - COPY --from=composer /usr/bin/composer /usr/bin/composer WORKDIR /app/ \ No newline at end of file diff --git a/build/php-7.0/Makefile b/build/php-7.0/Makefile new file mode 100644 index 0000000..62c467c --- /dev/null +++ b/build/php-7.0/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-7.0 test-php-7.0 test-suite-php-7.0 + +prepare-php-7.0: ## load dependencies with php 7.0 + docker compose run -T php-7.0 /usr/bin/composer update + +test-php-7.0: prepare-php-7.0 ## run tests against php 7.0 + docker compose run -T php-7.0 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-7.0: prepare-php-7.0 ## run suite tests against php 7.0, ex: make test-suite-php-7.0 SUITE="unit" + docker compose run -T php-7.0 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-7.1/Dockerfile b/build/php-7.1/Dockerfile new file mode 100644 index 0000000..2a0487e --- /dev/null +++ b/build/php-7.1/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:2.2 as composer +FROM php:7.1-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-7.1/Makefile b/build/php-7.1/Makefile new file mode 100644 index 0000000..26cbfad --- /dev/null +++ b/build/php-7.1/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-7.1 test-php-7.1 test-suite-php-7.1 + +prepare-php-7.1: ## load dependencies with php 7.1 + docker compose run -T php-7.1 /usr/bin/composer update + +test-php-7.1: prepare-php-7.1 ## run tests against php 7.1 + docker compose run -T php-7.1 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-7.1: prepare-php-7.1 ## run suite tests against php 7.1, ex: make test-suite-php-7.1 SUITE="unit" + docker compose run -T php-7.1 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-7.2/Dockerfile b/build/php-7.2/Dockerfile new file mode 100644 index 0000000..bd7b230 --- /dev/null +++ b/build/php-7.2/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:2.2 as composer +FROM php:7.2-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-7.2/Makefile b/build/php-7.2/Makefile new file mode 100644 index 0000000..6cb0833 --- /dev/null +++ b/build/php-7.2/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-7.2 test-php-7.2 test-suite-php-7.2 + +prepare-php-7.2: ## load dependencies with php 7.2 + docker compose run -T php-7.2 /usr/bin/composer update + +test-php-7.2: prepare-php-7.2 ## run tests against php 7.2 + docker compose run -T php-7.2 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-7.2: prepare-php-7.2 ## run suite tests against php 7.2, ex: make test-suite-php-7.2 SUITE="unit" + docker compose run -T php-7.2 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-7.3/Dockerfile b/build/php-7.3/Dockerfile new file mode 100644 index 0000000..28ef4fc --- /dev/null +++ b/build/php-7.3/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:2.2 as composer +FROM php:7.3-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-7.3/Makefile b/build/php-7.3/Makefile new file mode 100644 index 0000000..99e7817 --- /dev/null +++ b/build/php-7.3/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-7.3 test-php-7.3 test-suite-php-7.3 + +prepare-php-7.3: ## load dependencies with php 7.3 + docker compose run -T php-7.3 /usr/bin/composer update + +test-php-7.3: prepare-php-7.3 ## run tests against php 7.3 + docker compose run -T php-7.3 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-7.3: prepare-php-7.3 ## run suite tests against php 7.3, ex: make test-suite-php-7.3 SUITE="unit" + docker compose run -T php-7.3 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-7.4/Dockerfile b/build/php-7.4/Dockerfile new file mode 100644 index 0000000..d810922 --- /dev/null +++ b/build/php-7.4/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:2.2 as composer +FROM php:7.4-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-7.4/Makefile b/build/php-7.4/Makefile new file mode 100644 index 0000000..d68c343 --- /dev/null +++ b/build/php-7.4/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-7.4 test-php-7.4 test-suite-php-7.4 + +prepare-php-7.4: ## load dependencies with php 7.4 + docker compose run -T php-7.4 /usr/bin/composer update + +test-php-7.4: prepare-php-7.4 ## run tests against php 7.4 + docker compose run -T php-7.4 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-7.4: prepare-php-7.4 ## run suite tests against php 7.4, ex: make test-suite-php-7.4 SUITE="unit" + docker compose run -T php-7.4 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/Dockerfile.php8 b/build/php-8.0/Dockerfile similarity index 50% rename from Dockerfile.php8 rename to build/php-8.0/Dockerfile index c2916f9..04a6435 100644 --- a/Dockerfile.php8 +++ b/build/php-8.0/Dockerfile @@ -3,14 +3,7 @@ FROM php:8.0-cli-alpine RUN apk update && \ apk upgrade && \ - apk add --no-cache \ - autoconf \ - g++ \ - make - -RUN pecl install xdebug-3.0.2 && \ - pecl clear-cache && \ - docker-php-ext-enable xdebug + apk add --no-cache make COPY --from=composer /usr/bin/composer /usr/bin/composer diff --git a/build/php-8.0/Makefile b/build/php-8.0/Makefile new file mode 100644 index 0000000..b8b7eaf --- /dev/null +++ b/build/php-8.0/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-8.0 test-php-8.0 test-suite-php-8.0 + +prepare-php-8.0: ## load dependencies with php 8.0 + docker compose run -T php-8.0 /usr/bin/composer update + +test-php-8.0: prepare-php-8.0 ## run tests against php 8.0 + docker compose run -T php-8.0 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-8.0: prepare-php-8.0 ## run suite tests against php 8.0, ex: make test-suite-php-8.0 SUITE="unit" + docker compose run -T php-8.0 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-8.1/Dockerfile b/build/php-8.1/Dockerfile new file mode 100644 index 0000000..70aea88 --- /dev/null +++ b/build/php-8.1/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:latest as composer +FROM php:8.1-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-8.1/Makefile b/build/php-8.1/Makefile new file mode 100644 index 0000000..607d37b --- /dev/null +++ b/build/php-8.1/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-8.1 test-php-8.1 test-suite-php-8.1 + +prepare-php-8.1: ## load dependencies with php 8.1 + docker compose run -T php-8.1 /usr/bin/composer update + +test-php-8.1: prepare-php-8.1 ## run tests against php 8.1 + docker compose run -T php-8.1 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-8.1: prepare-php-8.1 ## run suite tests against php 8.1, ex: make test-suite-php-8.1 SUITE="unit" + docker compose run -T php-8.1 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-8.2/Dockerfile b/build/php-8.2/Dockerfile new file mode 100644 index 0000000..677d0ad --- /dev/null +++ b/build/php-8.2/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:latest as composer +FROM php:8.2-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-8.2/Makefile b/build/php-8.2/Makefile new file mode 100644 index 0000000..3867be5 --- /dev/null +++ b/build/php-8.2/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-8.2 test-php-8.2 test-suite-php-8.2 + +prepare-php-8.2: ## load dependencies with php 8.2 + docker compose run -T php-8.2 /usr/bin/composer update + +test-php-8.2: prepare-php-8.2 ## run tests against php 8.2 + docker compose run -T php-8.2 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-8.2: prepare-php-8.2 ## run suite tests against php 8.2, ex: make test-suite-php-8.2 SUITE="unit" + docker compose run -T php-8.2 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-8.3/Dockerfile b/build/php-8.3/Dockerfile new file mode 100644 index 0000000..94ac096 --- /dev/null +++ b/build/php-8.3/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:latest as composer +FROM php:8.3-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-8.3/Makefile b/build/php-8.3/Makefile new file mode 100644 index 0000000..886f5d4 --- /dev/null +++ b/build/php-8.3/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-8.3 test-php-8.3 test-suite-php-8.3 + +prepare-php-8.3: ## load dependencies with php 8.3 + docker compose run -T php-8.3 /usr/bin/composer update + +test-php-8.3: prepare-php-8.3 ## run tests against php 8.3 + docker compose run -T php-8.3 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-8.3: prepare-php-8.3 ## run suite tests against php 8.3, ex: make test-suite-php-8.3 SUITE="unit" + docker compose run -T php-8.3 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-8.4/Dockerfile b/build/php-8.4/Dockerfile new file mode 100644 index 0000000..ba691e0 --- /dev/null +++ b/build/php-8.4/Dockerfile @@ -0,0 +1,10 @@ +FROM composer:latest AS composer +FROM php:8.4-cli-alpine + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache make + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-8.4/Makefile b/build/php-8.4/Makefile new file mode 100644 index 0000000..bd4cfd0 --- /dev/null +++ b/build/php-8.4/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-8.4 test-php-8.4 test-suite-php-8.4 + +prepare-php-8.4: ## load dependencies with php 8.4 + docker compose run -T php-8.4 /usr/bin/composer update + +test-php-8.4: prepare-php-8.4 ## run tests against php 8.4 + docker compose run -T php-8.4 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-8.4: prepare-php-8.4 ## run suite tests against php 8.4, ex: make test-suite-php-8.4 SUITE="unit" + docker compose run -T php-8.4 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/build/php-8.5/Dockerfile b/build/php-8.5/Dockerfile new file mode 100644 index 0000000..c93d62d --- /dev/null +++ b/build/php-8.5/Dockerfile @@ -0,0 +1,6 @@ +FROM composer:latest AS composer +FROM php:8.5-cli-alpine + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +WORKDIR /app/ \ No newline at end of file diff --git a/build/php-8.5/Makefile b/build/php-8.5/Makefile new file mode 100644 index 0000000..3a38702 --- /dev/null +++ b/build/php-8.5/Makefile @@ -0,0 +1,10 @@ +.PHONY: prepare-php-8.5 test-php-8.5 test-suite-php-8.5 + +prepare-php-8.5: ## load dependencies with php 8.5 + docker compose run -T php-8.5 /usr/bin/composer update + +test-php-8.5: prepare-php-8.5 ## run tests against php 8.5 + docker compose run -T php-8.5 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml + +test-suite-php-8.5: prepare-php-8.5 ## run suite tests against php 8.5, ex: make test-suite-php-8.5 SUITE="unit" + docker compose run -T php-8.5 php vendor/bin/phpunit --configuration tests-resources/phpunit.xml --testsuite $(SUITE) \ No newline at end of file diff --git a/composer.json b/composer.json index 330fa8f..8c5dad5 100644 --- a/composer.json +++ b/composer.json @@ -16,21 +16,22 @@ } }, "require": { - "php": "^7 || ^8.0", + "php": "^7 || ~8.0 || ~8.1 || ~8.2 || ~8.3 || ~8.4 || ~8.5", "ext-json": "*", - "psr/log": "^1", - "psr/http-message": "^1", + "psr/log": "^1 || ^2 || ^3", + "psr/http-message": "~1.0 || ~1.1 || ~2.0", "psr/http-client": "^1", "psr/http-factory": "^1" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6 || ^8", + "phpunit/phpunit": "^6 || ~8.5", + "phpspec/prophecy": "^1.7", "symfony/yaml": "^3", "doctrine/instantiator": "1.0.5 || ^1.4.0", "phpdocumentor/reflection-docblock": "^4.3 || ^5.2.0", "phpdocumentor/type-resolver": "^0.5 || ^1.3.0", - "guzzlehttp/psr7": "^1", + "guzzlehttp/psr7": "^1 || ^2", "ext-curl": "*" }, "suggest": { diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f669f77..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3.5' - -services: - php: - build: . - volumes: - - .:/app - network_mode: host - diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 1a5512f..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - SMSAPI Coding Standard - - - - - */Standards/*/Tests/*\.(inc|css|js) - - - - - - **/tests/Unit/** - **/tests/Integration/** - - \ No newline at end of file diff --git a/src/Curl/Exception/ClientException.php b/src/Curl/Exception/ClientException.php index 4da31c7..0d2726a 100644 --- a/src/Curl/Exception/ClientException.php +++ b/src/Curl/Exception/ClientException.php @@ -4,27 +4,14 @@ namespace Smsapi\Client\Curl\Exception; -use Exception; -use Psr\Http\Client\ClientExceptionInterface; -use Psr\Http\Message\RequestInterface; +use Smsapi\Client\Infrastructure\HttpClient\ClientException as HttpClientException; /** * @api + * @deprecated + * @see HttpClientException */ -class ClientException extends Exception implements ClientExceptionInterface +class ClientException extends HttpClientException { - private $request; - public static function withRequest(string $message, RequestInterface $request): self - { - $exception = new self($message); - $exception->request = $request; - - return $exception; - } - - public function getRequest(): RequestInterface - { - return $this->request; - } } \ No newline at end of file diff --git a/src/Curl/Exception/NetworkException.php b/src/Curl/Exception/NetworkException.php index a4b7421..9d1729a 100644 --- a/src/Curl/Exception/NetworkException.php +++ b/src/Curl/Exception/NetworkException.php @@ -4,12 +4,14 @@ namespace Smsapi\Client\Curl\Exception; -use Psr\Http\Client\NetworkExceptionInterface; +use Smsapi\Client\Infrastructure\HttpClient\NetworkException as HttpClientNetworkException; /** * @api + * @deprecated + * @see HttpClientNetworkException */ -class NetworkException extends ClientException implements NetworkExceptionInterface +class NetworkException extends HttpClientNetworkException { } \ No newline at end of file diff --git a/src/Curl/Exception/RequestException.php b/src/Curl/Exception/RequestException.php index 2ea1aa5..0b96f1e 100644 --- a/src/Curl/Exception/RequestException.php +++ b/src/Curl/Exception/RequestException.php @@ -4,12 +4,14 @@ namespace Smsapi\Client\Curl\Exception; -use Psr\Http\Client\RequestExceptionInterface; +use Smsapi\Client\Infrastructure\HttpClient\RequestException as HttpClientRequestException; /** * @api + * @deprecated + * @see HttpClientRequestException */ -class RequestException extends ClientException implements RequestExceptionInterface +class RequestException extends HttpClientRequestException { } \ No newline at end of file diff --git a/src/Curl/HttpClient.php b/src/Curl/HttpClient.php index d068cf5..7bfadcb 100644 --- a/src/Curl/HttpClient.php +++ b/src/Curl/HttpClient.php @@ -8,8 +8,8 @@ use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Smsapi\Client\Curl\Exception\NetworkException; -use Smsapi\Client\Curl\Exception\RequestException; +use Smsapi\Client\Infrastructure\HttpClient\NetworkException; +use Smsapi\Client\Infrastructure\HttpClient\RequestException; /** * @internal @@ -34,7 +34,13 @@ public function sendRequest(RequestInterface $request): ResponseInterface private function prepareRequestHttpClient(RequestInterface $request) { - $url = sprintf("%s://%s%s", $request->getUri()->getScheme(), $request->getUri()->getHost(), $request->getRequestTarget()); + $url = strtr("{scheme}://{host}{port}{path}", [ + '{scheme}' => $request->getUri()->getScheme(), + '{host}' => $request->getUri()->getHost(), + '{port}' => $request->getUri()->getPort() ? ':' . $request->getUri()->getPort() : '', + '{path}' => $request->getRequestTarget() + ]); + $httpClient = curl_init($url); if ($httpClient === false) { @@ -88,7 +94,7 @@ private function execute(RequestInterface $request, $httpClient): ResponseInterf $headerSize = curl_getinfo($httpClient, CURLINFO_HEADER_SIZE); $headerString = substr($response, 0, $headerSize); - $headers = array_filter(explode("\n", $headerString), 'trim'); + $headers = HttpHeadersParser::parse($headerString); $body = substr($response, $headerSize); @@ -99,4 +105,4 @@ private function closeHttpClient($httpClient) { curl_close($httpClient); } -} \ No newline at end of file +} diff --git a/src/Curl/HttpHeadersParser.php b/src/Curl/HttpHeadersParser.php new file mode 100644 index 0000000..9ae2d69 --- /dev/null +++ b/src/Curl/HttpHeadersParser.php @@ -0,0 +1,23 @@ +httpClient->setLogger($logger); - } - public function smsapiPlService(string $apiToken): SmsapiPlService { - return $this->httpClient->smsapiPlService($apiToken); + return $this->httpClient()->smsapiPlService($apiToken); } public function smsapiPlServiceWithUri(string $apiToken, string $uri): SmsapiPlService { - return $this->httpClient->smsapiPlServiceWithUri($apiToken, $uri); + return $this->httpClient()->smsapiPlServiceWithUri($apiToken, $uri); } public function smsapiComService(string $apiToken): SmsapiComService { - return $this->httpClient->smsapiComService($apiToken); + return $this->httpClient()->smsapiComService($apiToken); } public function smsapiComServiceWithUri(string $apiToken, string $uri): SmsapiComService { - return $this->httpClient->smsapiComServiceWithUri($apiToken, $uri); + return $this->httpClient()->smsapiComServiceWithUri($apiToken, $uri); + } + + private function httpClient(): \Smsapi\Client\SmsapiHttpClient + { + if ($this->logger instanceof LoggerInterface) { + $this->httpClient->setLogger($this->logger); + } + + return $this->httpClient; } -} \ No newline at end of file +} diff --git a/src/Feature/Blacklist/Bag/CreateBlacklistedPhoneNumberBag.php b/src/Feature/Blacklist/Bag/CreateBlacklistedPhoneNumberBag.php index f51be56..571ef95 100644 --- a/src/Feature/Blacklist/Bag/CreateBlacklistedPhoneNumberBag.php +++ b/src/Feature/Blacklist/Bag/CreateBlacklistedPhoneNumberBag.php @@ -10,6 +10,7 @@ * @api * @property DateTimeInterface $expireAt */ +#[\AllowDynamicProperties] class CreateBlacklistedPhoneNumberBag { /** @var string */ diff --git a/src/Feature/Blacklist/Bag/DeleteBlacklistedPhoneNumberBag.php b/src/Feature/Blacklist/Bag/DeleteBlacklistedPhoneNumberBag.php index cdeb83b..e7ebf9e 100644 --- a/src/Feature/Blacklist/Bag/DeleteBlacklistedPhoneNumberBag.php +++ b/src/Feature/Blacklist/Bag/DeleteBlacklistedPhoneNumberBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class DeleteBlacklistedPhoneNumberBag { /** @var string */ diff --git a/src/Feature/Blacklist/Bag/FindBlacklistedPhoneNumbersBag.php b/src/Feature/Blacklist/Bag/FindBlacklistedPhoneNumbersBag.php index 635f79f..4e3fbee 100644 --- a/src/Feature/Blacklist/Bag/FindBlacklistedPhoneNumbersBag.php +++ b/src/Feature/Blacklist/Bag/FindBlacklistedPhoneNumbersBag.php @@ -10,6 +10,7 @@ * @api * @property string $q */ +#[\AllowDynamicProperties] class FindBlacklistedPhoneNumbersBag { use PaginationBag; diff --git a/src/Feature/Contacts/Bag/CreateContactBag.php b/src/Feature/Contacts/Bag/CreateContactBag.php index 1c67efb..54ffd65 100644 --- a/src/Feature/Contacts/Bag/CreateContactBag.php +++ b/src/Feature/Contacts/Bag/CreateContactBag.php @@ -16,6 +16,7 @@ * @property string $city * @property string $source */ +#[\AllowDynamicProperties] class CreateContactBag { /** diff --git a/src/Feature/Contacts/Bag/DeleteContactBag.php b/src/Feature/Contacts/Bag/DeleteContactBag.php index dacee6d..a3363c0 100644 --- a/src/Feature/Contacts/Bag/DeleteContactBag.php +++ b/src/Feature/Contacts/Bag/DeleteContactBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class DeleteContactBag { diff --git a/src/Feature/Contacts/Bag/FindContactBag.php b/src/Feature/Contacts/Bag/FindContactBag.php index 6198a1d..157a6bb 100644 --- a/src/Feature/Contacts/Bag/FindContactBag.php +++ b/src/Feature/Contacts/Bag/FindContactBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindContactBag { diff --git a/src/Feature/Contacts/Bag/FindContactsBag.php b/src/Feature/Contacts/Bag/FindContactsBag.php index ea83eb2..e75445b 100644 --- a/src/Feature/Contacts/Bag/FindContactsBag.php +++ b/src/Feature/Contacts/Bag/FindContactsBag.php @@ -18,6 +18,7 @@ * @property string $gender * @property string $birthdayDate */ +#[\AllowDynamicProperties] class FindContactsBag { use PaginationBag; diff --git a/src/Feature/Contacts/Bag/UpdateContactBag.php b/src/Feature/Contacts/Bag/UpdateContactBag.php index bfeed43..4458055 100644 --- a/src/Feature/Contacts/Bag/UpdateContactBag.php +++ b/src/Feature/Contacts/Bag/UpdateContactBag.php @@ -16,6 +16,7 @@ * @property string $city * @property string $source */ +#[\AllowDynamicProperties] class UpdateContactBag { diff --git a/src/Feature/Contacts/ContactsFeature.php b/src/Feature/Contacts/ContactsFeature.php index a1f2bfd..df9de39 100644 --- a/src/Feature/Contacts/ContactsFeature.php +++ b/src/Feature/Contacts/ContactsFeature.php @@ -19,6 +19,7 @@ interface ContactsFeature { /** * @return Contact[] + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 */ public function findContacts(FindContactsBag $findContactsBag = null): array; diff --git a/src/Feature/Contacts/ContactsHttpFeature.php b/src/Feature/Contacts/ContactsHttpFeature.php index 7d2f38c..7c73a56 100644 --- a/src/Feature/Contacts/ContactsHttpFeature.php +++ b/src/Feature/Contacts/ContactsHttpFeature.php @@ -31,6 +31,9 @@ public function __construct(RestRequestExecutor $restRequestExecutor, DataFactor $this->dataFactoryProvider = $dataFactoryProvider; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function findContacts(FindContactsBag $findContactsBag = null): array { $result = $this->restRequestExecutor->read('contacts', (array)$findContactsBag); diff --git a/src/Feature/Contacts/Fields/Bag/CreateContactFieldBag.php b/src/Feature/Contacts/Fields/Bag/CreateContactFieldBag.php index 1a45b1c..2269dcc 100644 --- a/src/Feature/Contacts/Fields/Bag/CreateContactFieldBag.php +++ b/src/Feature/Contacts/Fields/Bag/CreateContactFieldBag.php @@ -8,6 +8,7 @@ * @api * @property string $type */ +#[\AllowDynamicProperties] class CreateContactFieldBag { diff --git a/src/Feature/Contacts/Fields/Bag/DeleteContactFieldBag.php b/src/Feature/Contacts/Fields/Bag/DeleteContactFieldBag.php index ecdaabb..cbbb706 100644 --- a/src/Feature/Contacts/Fields/Bag/DeleteContactFieldBag.php +++ b/src/Feature/Contacts/Fields/Bag/DeleteContactFieldBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class DeleteContactFieldBag { diff --git a/src/Feature/Contacts/Fields/Bag/FindContactFieldOptionsBag.php b/src/Feature/Contacts/Fields/Bag/FindContactFieldOptionsBag.php index e48acc9..adf4d6d 100644 --- a/src/Feature/Contacts/Fields/Bag/FindContactFieldOptionsBag.php +++ b/src/Feature/Contacts/Fields/Bag/FindContactFieldOptionsBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindContactFieldOptionsBag { diff --git a/src/Feature/Contacts/Fields/Bag/UpdateContactFieldBag.php b/src/Feature/Contacts/Fields/Bag/UpdateContactFieldBag.php index 1dd2024..0e5cdbd 100644 --- a/src/Feature/Contacts/Fields/Bag/UpdateContactFieldBag.php +++ b/src/Feature/Contacts/Fields/Bag/UpdateContactFieldBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class UpdateContactFieldBag { diff --git a/src/Feature/Contacts/Groups/Bag/AssignContactToGroupBag.php b/src/Feature/Contacts/Groups/Bag/AssignContactToGroupBag.php index f4124ad..b51723e 100644 --- a/src/Feature/Contacts/Groups/Bag/AssignContactToGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/AssignContactToGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class AssignContactToGroupBag { diff --git a/src/Feature/Contacts/Groups/Bag/CreateGroupBag.php b/src/Feature/Contacts/Groups/Bag/CreateGroupBag.php index 4770a7c..80e934a 100644 --- a/src/Feature/Contacts/Groups/Bag/CreateGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/CreateGroupBag.php @@ -10,6 +10,7 @@ * @property string $idx * @property integer $contactExpireAfter */ +#[\AllowDynamicProperties] class CreateGroupBag { diff --git a/src/Feature/Contacts/Groups/Bag/DeleteGroupBag.php b/src/Feature/Contacts/Groups/Bag/DeleteGroupBag.php index d6f1bdf..cf7da05 100644 --- a/src/Feature/Contacts/Groups/Bag/DeleteGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/DeleteGroupBag.php @@ -8,6 +8,7 @@ * @api * @property bool $deleteContacts */ +#[\AllowDynamicProperties] class DeleteGroupBag { diff --git a/src/Feature/Contacts/Groups/Bag/FindContactGroupBag.php b/src/Feature/Contacts/Groups/Bag/FindContactGroupBag.php index 3c4c8e6..2ecfb43 100644 --- a/src/Feature/Contacts/Groups/Bag/FindContactGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/FindContactGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindContactGroupBag { diff --git a/src/Feature/Contacts/Groups/Bag/FindContactGroupsBag.php b/src/Feature/Contacts/Groups/Bag/FindContactGroupsBag.php index 9ff20f4..053a7bc 100644 --- a/src/Feature/Contacts/Groups/Bag/FindContactGroupsBag.php +++ b/src/Feature/Contacts/Groups/Bag/FindContactGroupsBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindContactGroupsBag { diff --git a/src/Feature/Contacts/Groups/Bag/FindGroupBag.php b/src/Feature/Contacts/Groups/Bag/FindGroupBag.php index 3b17dd4..b169b8a 100644 --- a/src/Feature/Contacts/Groups/Bag/FindGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/FindGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindGroupBag { diff --git a/src/Feature/Contacts/Groups/Bag/UnpinContactFromGroupBag.php b/src/Feature/Contacts/Groups/Bag/UnpinContactFromGroupBag.php index 550b81d..3b9000d 100644 --- a/src/Feature/Contacts/Groups/Bag/UnpinContactFromGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/UnpinContactFromGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class UnpinContactFromGroupBag { diff --git a/src/Feature/Contacts/Groups/Bag/UpdateGroupBag.php b/src/Feature/Contacts/Groups/Bag/UpdateGroupBag.php index ca9a3e4..2eb918f 100644 --- a/src/Feature/Contacts/Groups/Bag/UpdateGroupBag.php +++ b/src/Feature/Contacts/Groups/Bag/UpdateGroupBag.php @@ -10,6 +10,7 @@ * @property string $idx * @property integer $contactExpireAfter */ +#[\AllowDynamicProperties] class UpdateGroupBag { diff --git a/src/Feature/Contacts/Groups/Members/Bag/AddContactToGroupByQueryBag.php b/src/Feature/Contacts/Groups/Members/Bag/AddContactToGroupByQueryBag.php index 5d63613..a8ee959 100644 --- a/src/Feature/Contacts/Groups/Members/Bag/AddContactToGroupByQueryBag.php +++ b/src/Feature/Contacts/Groups/Members/Bag/AddContactToGroupByQueryBag.php @@ -15,6 +15,7 @@ * @property string $gender * @property string $birthdayDate */ +#[\AllowDynamicProperties] class AddContactToGroupByQueryBag { diff --git a/src/Feature/Contacts/Groups/Members/Bag/FindContactInGroupBag.php b/src/Feature/Contacts/Groups/Members/Bag/FindContactInGroupBag.php index ffa7b02..9238eaa 100644 --- a/src/Feature/Contacts/Groups/Members/Bag/FindContactInGroupBag.php +++ b/src/Feature/Contacts/Groups/Members/Bag/FindContactInGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindContactInGroupBag { diff --git a/src/Feature/Contacts/Groups/Members/Bag/MoveContactToGroupByQueryBag.php b/src/Feature/Contacts/Groups/Members/Bag/MoveContactToGroupByQueryBag.php index c7591b5..2d88a19 100644 --- a/src/Feature/Contacts/Groups/Members/Bag/MoveContactToGroupByQueryBag.php +++ b/src/Feature/Contacts/Groups/Members/Bag/MoveContactToGroupByQueryBag.php @@ -15,6 +15,7 @@ * @property string $gender * @property string $birthdayDate */ +#[\AllowDynamicProperties] class MoveContactToGroupByQueryBag { diff --git a/src/Feature/Contacts/Groups/Members/Bag/PinContactToGroupBag.php b/src/Feature/Contacts/Groups/Members/Bag/PinContactToGroupBag.php index be2f10a..9fe9cbb 100644 --- a/src/Feature/Contacts/Groups/Members/Bag/PinContactToGroupBag.php +++ b/src/Feature/Contacts/Groups/Members/Bag/PinContactToGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class PinContactToGroupBag { diff --git a/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupBag.php b/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupBag.php index 992a168..50cde48 100644 --- a/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupBag.php +++ b/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class UnpinContactFromGroupBag { diff --git a/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupByQueryBag.php b/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupByQueryBag.php index a7a8cd3..e36d5c8 100644 --- a/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupByQueryBag.php +++ b/src/Feature/Contacts/Groups/Members/Bag/UnpinContactFromGroupByQueryBag.php @@ -15,6 +15,7 @@ * @property string $gender * @property string $birthdayDate */ +#[\AllowDynamicProperties] class UnpinContactFromGroupByQueryBag { diff --git a/src/Feature/Contacts/Groups/Permissions/Bag/CreateGroupPermissionBag.php b/src/Feature/Contacts/Groups/Permissions/Bag/CreateGroupPermissionBag.php index 6305d3e..e48404a 100644 --- a/src/Feature/Contacts/Groups/Permissions/Bag/CreateGroupPermissionBag.php +++ b/src/Feature/Contacts/Groups/Permissions/Bag/CreateGroupPermissionBag.php @@ -10,6 +10,7 @@ * @property string $write * @property string $send */ +#[\AllowDynamicProperties] class CreateGroupPermissionBag { diff --git a/src/Feature/Contacts/Groups/Permissions/Bag/DeleteGroupPermissionBag.php b/src/Feature/Contacts/Groups/Permissions/Bag/DeleteGroupPermissionBag.php index f6ba1f2..591cad1 100644 --- a/src/Feature/Contacts/Groups/Permissions/Bag/DeleteGroupPermissionBag.php +++ b/src/Feature/Contacts/Groups/Permissions/Bag/DeleteGroupPermissionBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class DeleteGroupPermissionBag { diff --git a/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionBag.php b/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionBag.php index 4c285e0..150efff 100644 --- a/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionBag.php +++ b/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindGroupPermissionBag { diff --git a/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionsBag.php b/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionsBag.php index e7d2327..384d47e 100644 --- a/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionsBag.php +++ b/src/Feature/Contacts/Groups/Permissions/Bag/FindGroupPermissionsBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindGroupPermissionsBag { diff --git a/src/Feature/Contacts/Groups/Permissions/Bag/UpdateGroupPermissionBag.php b/src/Feature/Contacts/Groups/Permissions/Bag/UpdateGroupPermissionBag.php index 4a807e4..54fd14e 100644 --- a/src/Feature/Contacts/Groups/Permissions/Bag/UpdateGroupPermissionBag.php +++ b/src/Feature/Contacts/Groups/Permissions/Bag/UpdateGroupPermissionBag.php @@ -10,6 +10,7 @@ * @property string $write * @property string $send */ +#[\AllowDynamicProperties] class UpdateGroupPermissionBag { diff --git a/src/Feature/Hlr/Bag/SendHlrBag.php b/src/Feature/Hlr/Bag/SendHlrBag.php index fa7428a..991fc60 100644 --- a/src/Feature/Hlr/Bag/SendHlrBag.php +++ b/src/Feature/Hlr/Bag/SendHlrBag.php @@ -8,6 +8,7 @@ * @api * @property string $idx */ +#[\AllowDynamicProperties] class SendHlrBag { diff --git a/src/Feature/Mfa/Bag/CreateMfaBag.php b/src/Feature/Mfa/Bag/CreateMfaBag.php index 05036be..bafdac6 100644 --- a/src/Feature/Mfa/Bag/CreateMfaBag.php +++ b/src/Feature/Mfa/Bag/CreateMfaBag.php @@ -10,6 +10,7 @@ * @property string $content * @property bool $fast */ +#[\AllowDynamicProperties] class CreateMfaBag { /** diff --git a/src/Feature/Mfa/Bag/VerificationMfaBag.php b/src/Feature/Mfa/Bag/VerificationMfaBag.php index f1c8a7c..ba371e4 100644 --- a/src/Feature/Mfa/Bag/VerificationMfaBag.php +++ b/src/Feature/Mfa/Bag/VerificationMfaBag.php @@ -7,6 +7,7 @@ /** * @api */ +#[\AllowDynamicProperties] class VerificationMfaBag { /** diff --git a/src/Feature/Mms/Bag/SendMmsBag.php b/src/Feature/Mms/Bag/SendMmsBag.php index e3ce33a..6db03e1 100644 --- a/src/Feature/Mms/Bag/SendMmsBag.php +++ b/src/Feature/Mms/Bag/SendMmsBag.php @@ -7,6 +7,7 @@ * @api * @property bool $test */ +#[\AllowDynamicProperties] class SendMmsBag { diff --git a/src/Feature/ShortUrl/Bag/CreateShortUrlLinkBag.php b/src/Feature/ShortUrl/Bag/CreateShortUrlLinkBag.php index 7452f19..05b5e94 100644 --- a/src/Feature/ShortUrl/Bag/CreateShortUrlLinkBag.php +++ b/src/Feature/ShortUrl/Bag/CreateShortUrlLinkBag.php @@ -14,12 +14,13 @@ * @property DateTimeInterface $expire * @property string $description */ +#[\AllowDynamicProperties] class CreateShortUrlLinkBag { public static function withUrl(string $url): self { - $bag = new self(); + $bag = new CreateShortUrlLinkBag(); $bag->url = $url; return $bag; } diff --git a/src/Feature/Sms/Bag/DeleteScheduledSmssBag.php b/src/Feature/Sms/Bag/DeleteScheduledSmssBag.php index 748a5e6..51f6a09 100644 --- a/src/Feature/Sms/Bag/DeleteScheduledSmssBag.php +++ b/src/Feature/Sms/Bag/DeleteScheduledSmssBag.php @@ -4,6 +4,10 @@ namespace Smsapi\Client\Feature\Sms\Bag; +/** + * @api + */ +#[\AllowDynamicProperties] class DeleteScheduledSmssBag { /** @var array */ diff --git a/src/Feature/Sms/Bag/ScheduleSmsBag.php b/src/Feature/Sms/Bag/ScheduleSmsBag.php index dc328e9..2ce4b52 100644 --- a/src/Feature/Sms/Bag/ScheduleSmsBag.php +++ b/src/Feature/Sms/Bag/ScheduleSmsBag.php @@ -24,7 +24,9 @@ * @property string $param2 * @property string $param3 * @property string $param4 + * @property string $timeRestriction */ +#[\AllowDynamicProperties] class ScheduleSmsBag { /** @var string */ @@ -65,6 +67,9 @@ public function setParams(array $params): self return $this; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function setExternalId(string $idx, bool $checkIdx = null): self { $this->idx = [$idx]; diff --git a/src/Feature/Sms/Bag/ScheduleSmsToGroupBag.php b/src/Feature/Sms/Bag/ScheduleSmsToGroupBag.php index 0b39fa5..0981fd2 100644 --- a/src/Feature/Sms/Bag/ScheduleSmsToGroupBag.php +++ b/src/Feature/Sms/Bag/ScheduleSmsToGroupBag.php @@ -20,7 +20,9 @@ * @property bool $normalize * @property string $notifyUrl * @property bool $test + * @property string $timeRestriction */ +#[\AllowDynamicProperties] class ScheduleSmsToGroupBag { @@ -53,6 +55,9 @@ public static function withTemplateName(DateTimeInterface $scheduleAt, string $g return $bag; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function setExternalId(string $idx, bool $checkIdx = null): self { $this->idx = [$idx]; diff --git a/src/Feature/Sms/Bag/ScheduleSmssBag.php b/src/Feature/Sms/Bag/ScheduleSmssBag.php index 3e5fd10..b214567 100644 --- a/src/Feature/Sms/Bag/ScheduleSmssBag.php +++ b/src/Feature/Sms/Bag/ScheduleSmssBag.php @@ -25,10 +25,12 @@ * @property array $param2 * @property array $param3 * @property array $param4 + * @property string $timeRestriction */ +#[\AllowDynamicProperties] class ScheduleSmssBag { - /** @var array */ + /** @var array|string */ public $to; /** @var DateTimeInterface */ @@ -66,6 +68,9 @@ public function setParams(array $params): self return $this; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function setExternalId(array $idx, bool $checkIdx = null): self { $this->idx = $idx; diff --git a/src/Feature/Sms/Bag/SendSmsBag.php b/src/Feature/Sms/Bag/SendSmsBag.php index 181a00f..9ac48b5 100644 --- a/src/Feature/Sms/Bag/SendSmsBag.php +++ b/src/Feature/Sms/Bag/SendSmsBag.php @@ -24,7 +24,9 @@ * @property string $param2 * @property string $param3 * @property string $param4 + * @property string $timeRestriction */ +#[\AllowDynamicProperties] class SendSmsBag { /** @var string */ @@ -60,6 +62,9 @@ public function setParams(array $params): self return $this; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function setExternalId(string $idx, bool $checkIdx = null): self { $this->idx = [$idx]; diff --git a/src/Feature/Sms/Bag/SendSmsToGroupBag.php b/src/Feature/Sms/Bag/SendSmsToGroupBag.php index 52adfdf..a6da846 100644 --- a/src/Feature/Sms/Bag/SendSmsToGroupBag.php +++ b/src/Feature/Sms/Bag/SendSmsToGroupBag.php @@ -19,7 +19,9 @@ * @property bool $normalize * @property string $notifyUrl * @property bool $test + * @property string $timeRestriction */ +#[\AllowDynamicProperties] class SendSmsToGroupBag { /** @var string */ @@ -46,6 +48,9 @@ public static function withTemplateName(string $group, string $templateName): se return $bag; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function setExternalId(string $idx, bool $checkIdx = null): self { $this->idx = [$idx]; diff --git a/src/Feature/Sms/Bag/SendSmssBag.php b/src/Feature/Sms/Bag/SendSmssBag.php index fddbf5b..279bc53 100644 --- a/src/Feature/Sms/Bag/SendSmssBag.php +++ b/src/Feature/Sms/Bag/SendSmssBag.php @@ -25,10 +25,12 @@ * @property array $param2 * @property array $param3 * @property array $param4 + * @property string $timeRestriction */ +#[\AllowDynamicProperties] class SendSmssBag { - /** @var array */ + /** @var array|string */ public $to; /** @var string */ @@ -61,6 +63,9 @@ public function setParams(array $params): self return $this; } + /** + * @todo method signature to be changed in next major release as implicitly marking parameter as nullable is deprecated since PHP 8.4 + */ public function setExternalId(array $idx, bool $checkIdx = null): self { $this->idx = $idx; diff --git a/src/Feature/Sms/Sendernames/Bag/CreateSendernameBag.php b/src/Feature/Sms/Sendernames/Bag/CreateSendernameBag.php index 81d6f10..09d40c1 100644 --- a/src/Feature/Sms/Sendernames/Bag/CreateSendernameBag.php +++ b/src/Feature/Sms/Sendernames/Bag/CreateSendernameBag.php @@ -6,6 +6,7 @@ /** * @api */ +#[\AllowDynamicProperties] class CreateSendernameBag { public $sender; diff --git a/src/Feature/Sms/Sendernames/Bag/DeleteSendernameBag.php b/src/Feature/Sms/Sendernames/Bag/DeleteSendernameBag.php index 9f54dde..8e34293 100644 --- a/src/Feature/Sms/Sendernames/Bag/DeleteSendernameBag.php +++ b/src/Feature/Sms/Sendernames/Bag/DeleteSendernameBag.php @@ -6,6 +6,7 @@ /** * @api */ +#[\AllowDynamicProperties] class DeleteSendernameBag { public $sender; diff --git a/src/Feature/Sms/Sendernames/Bag/FindSendernameBag.php b/src/Feature/Sms/Sendernames/Bag/FindSendernameBag.php index cf4e58e..60abde0 100644 --- a/src/Feature/Sms/Sendernames/Bag/FindSendernameBag.php +++ b/src/Feature/Sms/Sendernames/Bag/FindSendernameBag.php @@ -6,6 +6,7 @@ /** * @api */ +#[\AllowDynamicProperties] class FindSendernameBag { public $sender; diff --git a/src/Feature/Sms/Sendernames/Bag/MakeSendernameDefaultBag.php b/src/Feature/Sms/Sendernames/Bag/MakeSendernameDefaultBag.php index 28539af..991e2d0 100644 --- a/src/Feature/Sms/Sendernames/Bag/MakeSendernameDefaultBag.php +++ b/src/Feature/Sms/Sendernames/Bag/MakeSendernameDefaultBag.php @@ -6,6 +6,7 @@ /** * @api */ +#[\AllowDynamicProperties] class MakeSendernameDefaultBag { public $sender; diff --git a/src/Feature/Sms/SmsHttpFeature.php b/src/Feature/Sms/SmsHttpFeature.php index 10dea8b..b2e2299 100644 --- a/src/Feature/Sms/SmsHttpFeature.php +++ b/src/Feature/Sms/SmsHttpFeature.php @@ -110,6 +110,8 @@ public function sendFlashSmsToGroup(SendSmsToGroupBag $sendSmsToGroupBag): array public function sendSmss(SendSmssBag $sendSmssBag): array { + $sendSmssBag->to = implode(',', $sendSmssBag->to); + return array_map( [$this->dataFactoryProvider->provideSmsFactory(), 'createFromObject'], $this->makeRequest($sendSmssBag)->list @@ -118,6 +120,8 @@ public function sendSmss(SendSmssBag $sendSmssBag): array public function sendFlashSmss(SendSmssBag $sendSmssBag): array { + $sendSmssBag->to = implode(',', $sendSmssBag->to); + return array_map( [$this->dataFactoryProvider->provideSmsFactory(), 'createFromObject'], $this->makeRequest($sendSmssBag)->list @@ -146,6 +150,7 @@ public function scheduleSms(ScheduleSmsBag $scheduleSmsBag): Sms public function scheduleSmss(ScheduleSmssBag $scheduleSmssBag): array { + $scheduleSmssBag->to = implode(',', $scheduleSmssBag->to); $scheduleSmssBag->dateValidate = true; return array_map( diff --git a/src/Feature/Subusers/Bag/CreateSubuserBag.php b/src/Feature/Subusers/Bag/CreateSubuserBag.php index 2ea4249..a45cb3c 100644 --- a/src/Feature/Subusers/Bag/CreateSubuserBag.php +++ b/src/Feature/Subusers/Bag/CreateSubuserBag.php @@ -8,6 +8,7 @@ * @property bool $active * @property string $description */ +#[\AllowDynamicProperties] class CreateSubuserBag { /** @var string */ diff --git a/src/Feature/Subusers/Bag/DeleteSubuserBag.php b/src/Feature/Subusers/Bag/DeleteSubuserBag.php index 65edc86..b5c741c 100644 --- a/src/Feature/Subusers/Bag/DeleteSubuserBag.php +++ b/src/Feature/Subusers/Bag/DeleteSubuserBag.php @@ -6,6 +6,7 @@ /** * @api */ +#[\AllowDynamicProperties] class DeleteSubuserBag { public $id; diff --git a/src/Feature/Subusers/Bag/UpdateSubuserBag.php b/src/Feature/Subusers/Bag/UpdateSubuserBag.php index f7eea08..fff8f26 100644 --- a/src/Feature/Subusers/Bag/UpdateSubuserBag.php +++ b/src/Feature/Subusers/Bag/UpdateSubuserBag.php @@ -11,6 +11,7 @@ * @property bool $active * @property string $description */ +#[\AllowDynamicProperties] class UpdateSubuserBag { /** @var string */ diff --git a/src/Feature/Vms/Bag/SendVmsBag.php b/src/Feature/Vms/Bag/SendVmsBag.php index a194a28..ef88cef 100644 --- a/src/Feature/Vms/Bag/SendVmsBag.php +++ b/src/Feature/Vms/Bag/SendVmsBag.php @@ -13,6 +13,7 @@ * @property bool $checkIdx * @property bool $test */ +#[\AllowDynamicProperties] class SendVmsBag { diff --git a/src/Infrastructure/HttpClient/ClientException.php b/src/Infrastructure/HttpClient/ClientException.php new file mode 100644 index 0000000..a17f164 --- /dev/null +++ b/src/Infrastructure/HttpClient/ClientException.php @@ -0,0 +1,30 @@ +request = $request; + + return $exception; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} \ No newline at end of file diff --git a/src/Infrastructure/HttpClient/Decorator/BaseUriDecorator.php b/src/Infrastructure/HttpClient/Decorator/BaseUriDecorator.php index 4747292..79cc2fc 100644 --- a/src/Infrastructure/HttpClient/Decorator/BaseUriDecorator.php +++ b/src/Infrastructure/HttpClient/Decorator/BaseUriDecorator.php @@ -7,6 +7,7 @@ use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Smsapi\Client\Infrastructure\HttpClient\RequestException; /** * @internal @@ -33,15 +34,22 @@ private function prependBaseUri(RequestInterface $request): RequestInterface { $uri = $request->getUri(); + if (!filter_var($this->baseUri, FILTER_VALIDATE_URL)) { + throw RequestException::withRequest("Invalid Base URI", $request); + } + $baseUriParts = parse_url($this->baseUri); $scheme = $baseUriParts['scheme'] ?? ''; $host = $baseUriParts['host'] ?? ''; - $path = $baseUriParts['path'] ?? ''; + $port = $baseUriParts['port'] ?? null; + $basePath = $baseUriParts['path'] ?? ''; + $basePath = rtrim($basePath, '/'); - $uri = $uri->withScheme($scheme); + $uri = $uri->withPath($basePath . '/' . $uri->getPath()); + $uri = $uri->withPort($port); $uri = $uri->withHost($host); - $uri = $uri->withPath($path . $uri->getPath()); + $uri = $uri->withScheme($scheme); return $request->withUri($uri); } diff --git a/src/Infrastructure/HttpClient/Decorator/LoggerDecorator.php b/src/Infrastructure/HttpClient/Decorator/LoggerDecorator.php index 926ce3f..decd283 100644 --- a/src/Infrastructure/HttpClient/Decorator/LoggerDecorator.php +++ b/src/Infrastructure/HttpClient/Decorator/LoggerDecorator.php @@ -26,13 +26,20 @@ public function __construct(ClientInterface $client, LoggerInterface $logger) public function sendRequest(RequestInterface $request): ResponseInterface { $this->logger->info('Request', [ + 'request' => $request, 'method' => $request->getMethod(), 'uri' => $request->getUri(), + 'headers' => $request->getHeaders(), + 'body' => $request->getBody()->getContents(), ]); $response = $this->client->sendRequest($request); - $this->logger->info('Response', ['response' => $response]); + $this->logger->info('Response', [ + 'response' => $response, + 'headers' => $response->getHeaders(), + 'body' => $response->getBody()->getContents(), + ]); return $response; } diff --git a/src/Infrastructure/HttpClient/NetworkException.php b/src/Infrastructure/HttpClient/NetworkException.php new file mode 100644 index 0000000..8464188 --- /dev/null +++ b/src/Infrastructure/HttpClient/NetworkException.php @@ -0,0 +1,15 @@ + 'json'] + $builtInParameters, $userParameters); return new Request($method, $path, $this->queryFormatter->format($parameters)); } diff --git a/src/Infrastructure/ResponseHttpCode.php b/src/Infrastructure/ResponseHttpCode.php index 2c201f5..4ef42b5 100644 --- a/src/Infrastructure/ResponseHttpCode.php +++ b/src/Infrastructure/ResponseHttpCode.php @@ -13,5 +13,6 @@ class ResponseHttpCode const CREATED = 201; const ACCEPTED = 202; const NO_CONTENT = 204; + const REQUEST_TIMEOUT = 408; const SERVICE_UNAVAILABLE = 503; } diff --git a/src/Infrastructure/ResponseMapper/RestResponseMapper.php b/src/Infrastructure/ResponseMapper/RestResponseMapper.php index fff15e3..04106a3 100644 --- a/src/Infrastructure/ResponseMapper/RestResponseMapper.php +++ b/src/Infrastructure/ResponseMapper/RestResponseMapper.php @@ -40,6 +40,8 @@ public function map(ResponseInterface $response): stdClass return new stdClass(); } elseif ($statusCode == ResponseHttpCode::SERVICE_UNAVAILABLE) { throw ApiErrorException::withMessageAndStatusCode('Service unavailable', $statusCode); + } elseif ($statusCode == ResponseHttpCode::REQUEST_TIMEOUT) { + throw ApiErrorException::withMessageAndStatusCode('Request timed out', $statusCode); } elseif ($contents) { $object = $this->jsonDecode->decode($contents); diff --git a/src/SmsapiClient.php b/src/SmsapiClient.php index 564843f..1c2bc2b 100644 --- a/src/SmsapiClient.php +++ b/src/SmsapiClient.php @@ -12,7 +12,7 @@ */ interface SmsapiClient extends LoggerAwareInterface { - const VERSION = 'Unreleased'; + const VERSION = '3.0.13'; public function smsapiPlService(string $apiToken): SmsapiPlService; diff --git a/tests-resources/config/config.dist.yml b/tests-resources/config/config.dist.yml index c3da0b8..7f9437f 100644 --- a/tests-resources/config/config.dist.yml +++ b/tests-resources/config/config.dist.yml @@ -1,3 +1,3 @@ Service name: SMSAPI.PL API URI: https://api.smsapi.pl -logger: false +API token: 0000000000000000000000000000000000000000 diff --git a/tests-resources/config/config.yml.example b/tests-resources/config/config.yml.example index ffbe66d..f93b66c 100644 --- a/tests-resources/config/config.yml.example +++ b/tests-resources/config/config.yml.example @@ -1,4 +1,3 @@ Service name: "SMSAPI.PL" OR "SMSAPI.COM" API token: 40-characters length string generated in SMSAPI Panel API URI: "https://api.smsapi.pl", "https://api.smsapi.com", "https://api2.smsapi.pl" or "https://api2.smsapi.com" -logger: true diff --git a/phpunit.dist.xml b/tests-resources/phpunit.dist.xml similarity index 53% rename from phpunit.dist.xml rename to tests-resources/phpunit.dist.xml index 95cb746..4d4acd5 100644 --- a/phpunit.dist.xml +++ b/tests-resources/phpunit.dist.xml @@ -1,7 +1,7 @@ - tests + ../tests - tests/Unit + ../tests/Unit - tests/Integration + ../tests/Integration - tests/Integration/Feature/Contacts - tests/Unit/Feature/Contacts + ../tests/Integration/Feature/Contacts + ../tests/Unit/Feature/Contacts - tests/Integration/Feature/Hlr + ../tests/Integration/Feature/Hlr - tests/Integration/Feature/Mms + ../tests/Integration/Feature/Mms - tests/Integration/Feature/Ping + ../tests/Integration/Feature/Ping - tests/Integration/Feature/Profile + ../tests/Integration/Feature/Profile - tests/Unit/Feature/Push + ../tests/Unit/Feature/Push - tests/Integration/Feature/Sendernames + ../tests/Integration/Feature/Sendernames - tests/Integration/Feature/ShortUrl + ../tests/Integration/Feature/ShortUrl - tests/Integration/Feature/Sms - tests/Unit/Feature/Sms + ../tests/Integration/Feature/Sms + ../tests/Unit/Feature/Sms - tests/Integration/Feature/Mfa + ../tests/Integration/Feature/Mfa - tests/Integration/Feature/Subusers + ../tests/Integration/Feature/Subusers - tests/Integration/Feature/Vms + ../tests/Integration/Feature/Vms - tests/Integration/Feature/Blacklist + ../tests/Integration/Feature/Blacklist + + + + + + diff --git a/tests/Fixture/PhoneNumberFixture.php b/tests/Fixture/PhoneNumberFixture.php index 2ab2941..9f06e1f 100644 --- a/tests/Fixture/PhoneNumberFixture.php +++ b/tests/Fixture/PhoneNumberFixture.php @@ -20,4 +20,9 @@ public static function anyValidMobile(): string { return (string)((int)self::$validMobile + self::$i++); } + + public static function xValidMobile(int $x): array + { + return array_map(function () {return self::anyValidMobile();}, range(1, $x)); + } } diff --git a/tests/Helper/HttpClient/HttpClientRequestSpy.php b/tests/Helper/HttpClient/HttpClientRequestSpy.php new file mode 100644 index 0000000..375e984 --- /dev/null +++ b/tests/Helper/HttpClient/HttpClientRequestSpy.php @@ -0,0 +1,27 @@ +lastSentRequest = $request; + + return new Response(); + } + + public function getLastSentRequest(): RequestInterface + { + return $this->lastSentRequest; + } +} \ No newline at end of file diff --git a/tests/Integration/Feature/Mfa/MfaFeatureTest.php b/tests/Integration/Feature/Mfa/MfaFeatureTest.php index 7fdbc3c..1a01cfb 100644 --- a/tests/Integration/Feature/Mfa/MfaFeatureTest.php +++ b/tests/Integration/Feature/Mfa/MfaFeatureTest.php @@ -32,7 +32,7 @@ public function it_should_create_mfa() /** * @test */ - public function it_should_not_create_mfa_for_an_ivalid_mobile_phone_number() + public function it_should_not_create_mfa_for_an_invalid_mobile_phone_number() { //given $mfaFeature = self::$smsapiService->mfaFeature(); @@ -97,7 +97,7 @@ public function it_should_not_verify_invalid_code() { //given $mfaFeature = self::$smsapiService->mfaFeature(); - $verificationMfaBag = new VerificationMfaBag('123 456', PhoneNumberFixture::anyValidMobile()); + $verificationMfaBag = new VerificationMfaBag('invalid', PhoneNumberFixture::anyValidMobile()); //expect $this->expectException(SmsapiClientException::class); $this->expectExceptionMessage('MFA code has invalid format'); diff --git a/tests/Integration/Feature/Sms/SmsFeatureTest.php b/tests/Integration/Feature/Sms/SmsFeatureTest.php index e78a420..c5051f8 100644 --- a/tests/Integration/Feature/Sms/SmsFeatureTest.php +++ b/tests/Integration/Feature/Sms/SmsFeatureTest.php @@ -82,12 +82,12 @@ public function it_should_send_flash_sms() public function it_should_send_smss() { $smsFeature = self::$smsapiService->smsFeature(); - $sendSmssBag = $this->givenSmssToSend(); + $sendSmssBag = $this->givenSmssToSend(2); $sendSmssBag->test = true; $results = $smsFeature->sendSmss($sendSmssBag); - $this->assertCount(count($sendSmssBag->to), $results); + $this->assertCount(2, $results); } /** @@ -96,12 +96,12 @@ public function it_should_send_smss() public function it_should_send_flash_smss() { $smsFeature = self::$smsapiService->smsFeature(); - $sendSmssBag = $this->givenSmssToSend(); + $sendSmssBag = $this->givenSmssToSend(2); $sendSmssBag->test = true; $results = $smsFeature->sendFlashSmss($sendSmssBag); - $this->assertCount(count($sendSmssBag->to), $results); + $this->assertCount(2, $results); } /** @@ -110,7 +110,7 @@ public function it_should_send_flash_smss() public function it_should_not_receive_content_details_for_smss() { $smsFeature = self::$smsapiService->smsFeature(); - $sendSmsesBag = $this->givenSmssToSend(); + $sendSmsesBag = $this->givenSmssToSend(2); $sendSmsesBag->test = true; /** @var Sms[] $results */ @@ -142,12 +142,12 @@ public function it_should_schedule_sms() public function it_should_schedule_smss() { $smsFeature = self::$smsapiService->smsFeature(); - $scheduleSmssBag = $this->givenSmssToSchedule(); + $scheduleSmssBag = $this->givenSmssToSchedule(2); $scheduleSmssBag->test = true; $results = $smsFeature->scheduleSmss($scheduleSmssBag); - $this->assertCount(count($scheduleSmssBag->to), $results); + $this->assertCount(2, $results); } /** @@ -171,7 +171,7 @@ public function it_should_schedule_flash_sms() public function it_should_delete_scheduled_smss() { $smsFeature = self::$smsapiService->smsFeature(); - $scheduleSmssBag = $this->givenSmssToSchedule(); + $scheduleSmssBag = $this->givenSmssToSchedule(2); $results = $smsFeature->scheduleSmss($scheduleSmssBag); $smsIds = array_map(function (Sms $sms) { @@ -190,12 +190,9 @@ private function givenSmsToSend(): SendSmsBag return SendSmsBag::withMessage($someReceiver, 'some message'); } - private function givenSmssToSend(): SendSmssBag + private function givenSmssToSend(int $x): SendSmssBag { - $receivers = [ - PhoneNumberFixture::anyValidMobile(), - PhoneNumberFixture::anyValidMobile(), - ]; + $receivers = PhoneNumberFixture::xValidMobile($x); return SendSmssBag::withMessage($receivers, 'some message'); } @@ -206,13 +203,10 @@ private function givenSmsToSchedule(): ScheduleSmsBag return ScheduleSmsBag::withMessage($someDate, $someReceiver, 'some message'); } - private function givenSmssToSchedule(): ScheduleSmssBag + private function givenSmssToSchedule(int $x): ScheduleSmssBag { $someDate = new DateTime('+1 day noon'); - $receivers = [ - PhoneNumberFixture::anyValidMobile(), - PhoneNumberFixture::anyValidMobile(), - ]; + $receivers = PhoneNumberFixture::xValidMobile($x); return ScheduleSmssBag::withMessage($someDate, $receivers, 'some message'); } } diff --git a/tests/SmsapiClientIntegrationTestCase.php b/tests/SmsapiClientIntegrationTestCase.php index 6c547c8..09cc561 100644 --- a/tests/SmsapiClientIntegrationTestCase.php +++ b/tests/SmsapiClientIntegrationTestCase.php @@ -27,16 +27,8 @@ public static function prepare() $apiUri = Config::get('API URI'); - if (!filter_var($apiUri, FILTER_VALIDATE_URL)) { - throw new RuntimeException('Invalid API URI'); - } - $smsapiHttpClient = new SmsapiHttpClient(); - if (Config::get('logger')) { - $smsapiHttpClient->setLogger(new TestLogger()); - } - $serviceName = Config::get('Service name'); if ($serviceName === ServiceName::SMSAPI_PL) { self::$smsapiService = $smsapiHttpClient->smsapiPlServiceWithUri(self::$apiToken, $apiUri); diff --git a/tests/TestLogger.php b/tests/TestLogger.php deleted file mode 100644 index 3ff6519..0000000 --- a/tests/TestLogger.php +++ /dev/null @@ -1,37 +0,0 @@ - $value) { - if ($value instanceof RequestInterface) { - $context[$item] = [ - 'headers' => $value->getHeaders(), - 'uri' => $value->getUri()->__toString(), - 'method' => $value->getMethod(), - 'contents' => $value->getBody()->__toString(), - ]; - } elseif ($value instanceof ResponseInterface) { - $context[$item] = [ - 'headers' => $value->getHeaders(), - 'status_code' => $value->getStatusCode(), - 'contents' => $value->getBody()->__toString(), - ]; - } - } - - echo sprintf("[%s] %s (%s)\n", $level, $message, print_r($context)); - } -} diff --git a/tests/Unit/Curl/HttpHeadersParserTest.php b/tests/Unit/Curl/HttpHeadersParserTest.php new file mode 100644 index 0000000..cdfdbd8 --- /dev/null +++ b/tests/Unit/Curl/HttpHeadersParserTest.php @@ -0,0 +1,85 @@ +assertArrayNotHasKey('HTTP/1.1 202 OK', $headers); + $this->assertNotContains('HTTP/1.1 202 OK', $headers); + } + + /** + * @test + */ + public function not_bypass_non_http_status_line_first_line() + { + $rawHeaders = "Header1: any\r\nHeader2: any, other\r\n"; + + $headers = HttpHeadersParser::parse($rawHeaders); + + $this->assertArrayHasKey('Header1', $headers); + } + + /** + * @test + */ + public function bypass_empty_line() + { + $rawHeaders = "HTTP/1.1 202 OK\r\nHeader1: any\r\nHeader2: any, other\r\n\r\n"; + + $headers = HttpHeadersParser::parse($rawHeaders); + + $this->assertCount(2, array_keys($headers)); + } + + /** + * @test + */ + public function add_all_headers() + { + $rawHeaders = "HTTP/1.1 202 OK\r\nHeader1: any\r\nHeader2: any, other\r\n"; + + $headers = HttpHeadersParser::parse($rawHeaders); + + $this->assertArrayHasKey('Header1', $headers); + $this->assertArrayHasKey('Header2', $headers); + } + + /** + * @test + */ + public function add_single_value_headers() + { + $rawHeaders = "HTTP/1.1 202 OK\r\nHeader1: any\r\nHeader2: any, other\r\n"; + + $headers = HttpHeadersParser::parse($rawHeaders); + + $this->assertEquals('any', $headers['Header1']); + } + + /** + * @test + */ + public function add_multi_value_headers() + { + $rawHeaders = "HTTP/1.1 202 OK\r\nHeader1: any\r\nHeader2: any, other\r\n"; + + $headers = HttpHeadersParser::parse($rawHeaders); + + $this->assertEquals('any, other', $headers['Header2']); + } +} \ No newline at end of file diff --git a/tests/Unit/Infrastructure/HttpClient/Decorator/BaseUriDecoratorTest.php b/tests/Unit/Infrastructure/HttpClient/Decorator/BaseUriDecoratorTest.php new file mode 100644 index 0000000..0e538bd --- /dev/null +++ b/tests/Unit/Infrastructure/HttpClient/Decorator/BaseUriDecoratorTest.php @@ -0,0 +1,162 @@ +sendRequestToAnyEndpoint($decorator); + + $this->assertEquals($expectedRequestUri, (string)$sentRequestSpy->getLastSentRequest()->getUri()); + $this->assertEquals($expectedRequestSchema, $sentRequestSpy->getLastSentRequest()->getUri()->getScheme()); + } + + /** + * @test + * @testWith + * ["example.com"] + * ["example.com/base/"] + * ["example.com:80"] + * ["example.com:80/base/"] + * ["any://"] + * ["any:///"] + * ["any://:80/"] + */ + public function dont_send_request_without_base_schema_or_host(string $baseUri) + { + $sentRequestSpy = new HttpClientRequestSpy(); + $decorator = new BaseUriDecorator($sentRequestSpy, $baseUri); + + $this->expectException(RequestException::class); + $this->expectExceptionMessage('Invalid Base URI'); + $this->sendRequestToAnyEndpoint($decorator); + } + + /** + * @test + * @testWith + * ["any://example.com", "any://example.com/endpoint", "example.com"] + * ["any://example.com:80", "any://example.com:80/endpoint", "example.com"] + * ["any://example", "any://example/endpoint", "example"] + * ["any://example:80", "any://example:80/endpoint", "example"] + */ + public function send_request_with_base_host(string $baseUri, string $expectedRequestUri, string $expectedRequestHost) + { + $sentRequestSpy = new HttpClientRequestSpy(); + $decorator = new BaseUriDecorator($sentRequestSpy, $baseUri); + + $this->sendRequestToAnyEndpoint($decorator); + + $this->assertEquals($expectedRequestUri, (string)$sentRequestSpy->getLastSentRequest()->getUri()); + $this->assertEquals($expectedRequestHost, $sentRequestSpy->getLastSentRequest()->getUri()->getHost()); + } + + /** + * @test + * @testWith + * ["any://example.com:80", "any://example.com:80/endpoint", "80"] + * ["any://example:80", "any://example:80/endpoint", "80"] + */ + public function send_request_with_base_port(string $baseUri, string $expectedRequestUri, string $expectedRequestPort) + { + $sentRequestSpy = new HttpClientRequestSpy(); + $decorator = new BaseUriDecorator($sentRequestSpy, $baseUri); + + $this->sendRequestToAnyEndpoint($decorator); + + $this->assertEquals($expectedRequestUri, (string)$sentRequestSpy->getLastSentRequest()->getUri()); + $this->assertEquals($expectedRequestPort, $sentRequestSpy->getLastSentRequest()->getUri()->getPort()); + } + + /** + * @test + * @testWith + * ["any://example.com", "any://example.com/endpoint"] + * ["any://example", "any://example/endpoint"] + * ["any://example.com/base", "any://example.com/base/endpoint"] + * ["any://example/base", "any://example/base/endpoint"] + */ + public function send_request_without_base_port(string $baseUri, string $expectedRequestUri) + { + $sentRequestSpy = new HttpClientRequestSpy(); + $decorator = new BaseUriDecorator($sentRequestSpy, $baseUri); + + $this->sendRequestToAnyEndpoint($decorator); + + $this->assertEquals($expectedRequestUri, (string)$sentRequestSpy->getLastSentRequest()->getUri()); + $this->assertEquals('', $sentRequestSpy->getLastSentRequest()->getUri()->getPort()); + } + + /** + * @test + * @testWith + * ["any://example.com/base", "any://example.com/base/endpoint", "/base/endpoint"] + * ["any://example.com:80/base/", "any://example.com:80/base/endpoint", "/base/endpoint"] + * ["any://example:80/base", "any://example:80/base/endpoint", "/base/endpoint"] + * ["any://example.com/base/", "any://example.com/base/endpoint", "/base/endpoint"] + * ["any://example/base/", "any://example/base/endpoint", "/base/endpoint"] + * ["any://example.com:80/base/", "any://example.com:80/base/endpoint", "/base/endpoint"] + * ["any://example:80/base", "any://example:80/base/endpoint", "/base/endpoint"] + */ + public function send_request_with_base_path(string $baseUri, string $expectedRequestUri, string $expectedRequestPath) + { + $sentRequestSpy = new HttpClientRequestSpy(); + $decorator = new BaseUriDecorator($sentRequestSpy, $baseUri); + + $this->sendRequestToAnyEndpoint($decorator); + + $this->assertEquals($expectedRequestUri, (string)$sentRequestSpy->getLastSentRequest()->getUri()); + $this->assertEquals($expectedRequestPath, $sentRequestSpy->getLastSentRequest()->getUri()->getPath()); + } + + /** + * @test + * @testWith + * ["any://example.com", "any://example.com/endpoint", "/endpoint"] + * ["any://example", "any://example/endpoint", "/endpoint"] + * ["any://example.com:80", "any://example.com:80/endpoint", "/endpoint"] + * ["any://example:80", "any://example:80/endpoint", "/endpoint"] + * ["any://example.com/", "any://example.com/endpoint", "/endpoint"] + * ["any://example/", "any://example/endpoint", "/endpoint"] + * ["any://example.com:80/", "any://example.com:80/endpoint", "/endpoint"] + * ["any://example:80/", "any://example:80/endpoint", "/endpoint"] + */ + public function send_request_without_base_path(string $baseUri, string $expectedRequestUri, string $expectedRequestPath) + { + $sentRequestSpy = new HttpClientRequestSpy(); + $decorator = new BaseUriDecorator($sentRequestSpy, $baseUri); + + $this->sendRequestToAnyEndpoint($decorator); + + $this->assertEquals($expectedRequestUri, (string)$sentRequestSpy->getLastSentRequest()->getUri()); + $this->assertEquals($expectedRequestPath, $sentRequestSpy->getLastSentRequest()->getUri()->getPath()); + } + + private function sendRequestToAnyEndpoint(BaseUriDecorator $decorator) + { + $request = new Request('ANY', 'endpoint'); + $decorator->sendRequest($request); + } +} \ No newline at end of file diff --git a/tests/Unit/Infrastructure/RequestMapper/LegacyRequestMapperTest.php b/tests/Unit/Infrastructure/RequestMapper/LegacyRequestMapperTest.php index 63f910f..9b5246a 100644 --- a/tests/Unit/Infrastructure/RequestMapper/LegacyRequestMapperTest.php +++ b/tests/Unit/Infrastructure/RequestMapper/LegacyRequestMapperTest.php @@ -25,10 +25,69 @@ public function init() /** * @test */ - public function it_should_create_post_request_with_parameters() + public function it_should_use_path_as_request_uri() { $path = 'anyPath'; + $request = $this->mapper->map($path, []); + + $this->assertEquals($path, $request->getUri()); + } + + /** + * @test + */ + public function it_should_send_request_as_post() + { + $request = $this->mapper->map('anyPath', []); + + $this->assertEquals(RequestHttpMethod::POST, $request->getMethod()); + } + + /** + * @test + */ + public function it_should_always_set_format_json_parameter() + { + $builtInParameters = []; + $userParameters = []; + + $request = $this->mapper->map('anyPath', $builtInParameters, $userParameters); + + $this->assertEquals('format=json', $request->getBody()); + } + + /** + * @test + */ + public function it_should_prepend_format_parameter_to_built_in_parameters_when_none() + { + $builtInParameters = []; + $userParameters = ['any2' => 'any']; + + $request = $this->mapper->map('anyPath', $builtInParameters, $userParameters); + + $this->assertEquals('format=json&any2=any', $request->getBody()); + } + + /** + * @test + */ + public function it_should_prepend_format_parameter_to_built_in_parameters_when_set() + { + $builtInParameters = ['any1' => 'any']; + $userParameters = []; + + $request = $this->mapper->map('anyPath', $builtInParameters, $userParameters); + + $this->assertEquals('format=json&any1=any', $request->getBody()); + } + + /** + * @test + */ + public function it_should_merge_both_built_in_and_user_parameters() + { $builtInParameters = [ 'any1' => 'any', ]; @@ -36,11 +95,8 @@ public function it_should_create_post_request_with_parameters() 'any2' => 'any', ]; - $request = $this->mapper->map($path, $builtInParameters, $userParameters); - - $this->assertEquals($path, $request->getUri()); - $this->assertEquals(RequestHttpMethod::POST, $request->getMethod()); + $request = $this->mapper->map('anyPath', $builtInParameters, $userParameters); - $this->assertEquals('any1=any&format=json&any2=any', $request->getBody()); + $this->assertEquals('format=json&any1=any&any2=any', $request->getBody()); } } diff --git a/tests/Unit/Infrastructure/ResponseMapper/RestResponseMapperTest.php b/tests/Unit/Infrastructure/ResponseMapper/RestResponseMapperTest.php index dfbb087..9419e90 100644 --- a/tests/Unit/Infrastructure/ResponseMapper/RestResponseMapperTest.php +++ b/tests/Unit/Infrastructure/ResponseMapper/RestResponseMapperTest.php @@ -92,6 +92,18 @@ public function it_should_throw_exception_on_service_unavailable() $this->restResponseMapper->map($responseWithServiceUnavailable); } + /** + * @test + */ + public function it_should_throw_exception_on_request_timeout() + { + $responseWithServiceUnavailable = new Response(ResponseHttpCode::REQUEST_TIMEOUT); + + $this->expectException(ApiErrorException::class); + $this->expectExceptionMessage("Request timed out"); + $this->restResponseMapper->map($responseWithServiceUnavailable); + } + /** * @test */