From 328c4fcc22544f9cede2f9bada2375538e5a101f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Salo=C5=88?= Date: Fri, 25 Jun 2021 15:11:14 +0200 Subject: [PATCH] New v4-beta version of MP API client --- .editorconfig | 9 + .env.dist | 3 + .gitattributes | 4 + .gitignore | 4 + .gitlab-ci.yml | 120 + CHANGELOG.md | 4 + Dockerfile.php-7_4 | 21 + LICENSE | 201 + Makefile | 88 + README.md | 134 + codeception.yml | 22 + composer.json | 45 + composer.lock | 6107 +++++++++++++++++ doc/Article.md | 9 + doc/Brand.md | 39 + doc/Category.md | 167 + doc/Checks.md | 91 + doc/Exception.md | 66 + doc/Financial.md | 320 + doc/Label.md | 39 + doc/Order.md | 9 + doc/Shop.md | 43 + doc/SupplyDelay.md | 175 + example/Brand.php | 44 + example/Category.php | 94 + example/Checks.php | 89 + example/Exception.php | 50 + example/Financial.php | 143 + example/Label.php | 44 + example/Order.php | 66 + example/Products.php | 55 + example/Shop.php | 55 + example/SupplyDelay.php | 218 + phpcs-ruleset.xml | 106 + phpmd.xml | 66 + phpstan.neon | 31 + psalm.xml | 15 + src/Article/ArticleClient.php | 167 + src/Article/DTO/AbstractArticleRequest.php | 261 + src/Article/DTO/BatchAvailability.php | 36 + src/Article/DTO/BatchAvailabilityIterator.php | 52 + src/Article/DTO/ProductRequest.php | 190 + src/Article/DTO/VariantRequest.php | 106 + src/Article/DTO/VariantRequestIterator.php | 52 + src/Article/Entity/BasicProduct.php | 140 + src/Article/Entity/BasicProductList.php | 45 + src/Article/Entity/BasicVariant.php | 113 + src/Article/Entity/BasicVariantList.php | 45 + src/Article/Entity/Common/AbstractArticle.php | 240 + src/Article/Entity/Common/Availability.php | 66 + src/Article/Entity/Common/Dimensions.php | 94 + src/Article/Entity/Common/Label.php | 85 + src/Article/Entity/Common/LabelIterator.php | 66 + src/Article/Entity/Common/Media.php | 109 + src/Article/Entity/Common/MediaIterator.php | 67 + src/Article/Entity/Common/Override.php | 65 + .../Entity/Common/OverrideIterator.php | 58 + src/Article/Entity/Common/PackageSizeEnum.php | 24 + src/Article/Entity/Common/Parameter.php | 78 + .../Entity/Common/ParameterIterator.php | 72 + src/Article/Entity/Common/Pricing.php | 65 + src/Article/Entity/Common/Promotion.php | 93 + .../Entity/Common/PromotionIterator.php | 54 + src/Article/Entity/Common/StatusEnum.php | 27 + src/Article/Entity/Product.php | 209 + src/Article/Entity/ProductStageEnum.php | 24 + src/Article/Entity/Variant.php | 55 + src/Article/Entity/VariantIterator.php | 48 + src/Brand/BrandClient.php | 19 + src/Brand/Entity/Brand.php | 45 + src/Brand/Entity/BrandIterator.php | 46 + src/Category/CategoryClient.php | 36 + src/Category/Entity/Category.php | 45 + src/Category/Entity/CategoryIterator.php | 46 + src/Category/Entity/Parameter.php | 66 + src/Category/Entity/ParameterIterator.php | 46 + src/Category/Entity/ParameterValue.php | 45 + .../Entity/ParameterValueIterator.php | 46 + src/Category/Entity/TreeItem.php | 88 + src/Category/Entity/TreeItemIterator.php | 56 + src/Category/Entity/TreeMenuConstraint.php | 70 + .../Entity/TreeMenuConstraintIterator.php | 44 + src/Category/Entity/TreeMenuItem.php | 69 + src/Category/Entity/TreeMenuItemIterator.php | 44 + src/Category/Entity/TreeSapCategory.php | 61 + .../Entity/TreeSapCategoryIterator.php | 44 + src/Checks/ChecksClient.php | 25 + src/Checks/Entity/Error.php | 82 + src/Checks/Entity/ErrorIterator.php | 44 + src/Common/AbstractMpApiClient.php | 103 + .../Authenticators/ClientIdAuthenticator.php | 32 + src/Common/DTO/Paging.php | 61 + .../Interfaces/ArticleClientInterface.php | 128 + .../Interfaces/AuthMiddlewareInterface.php | 14 + .../Interfaces/BrandClientInterface.php | 16 + .../Interfaces/CategoryClientInterface.php | 29 + .../Interfaces/ChecksClientInterface.php | 21 + src/Common/Interfaces/ClientInterface.php | 12 + .../Interfaces/FinancialClientInterface.php | 51 + .../Interfaces/LabelClientInterface.php | 16 + .../Interfaces/MpApiClientInterface.php | 30 + .../Interfaces/OrderClientInterface.php | 67 + src/Common/Interfaces/ShopClientInterface.php | 16 + .../Interfaces/SupplyDelayClientInterface.php | 63 + src/Common/Util/AbstractIntKeyIterator.php | 87 + src/Common/Util/AbstractList.php | 133 + src/Common/Util/AbstractStringEnum.php | 104 + src/Common/Util/AbstractStringKeyIterator.php | 87 + src/Common/Util/DataTypeUtil.php | 51 + src/Common/Util/InputDataUtil.php | 82 + src/Common/Util/JsonSerializeEntityTrait.php | 29 + src/Exception/BadResponseException.php | 71 + src/Exception/ErrorCode.php | 60 + src/Exception/ExceptionFactory.php | 44 + src/Exception/IncorrectDataTypeException.php | 8 + src/Exception/MpApiException.php | 10 + src/Exception/NotFoundException.php | 8 + src/Exception/TooManyRequestsException.php | 8 + src/Exception/UnauthorizedException.php | 8 + src/Filter/Filter.php | 163 + src/Filter/FilterItem.php | 75 + src/Filter/FilterOperatorEnum.php | 46 + src/Financial/Entity/Common/Address.php | 61 + src/Financial/Entity/Common/Attachment.php | 45 + src/Financial/Entity/Common/Customer.php | 77 + src/Financial/Entity/Common/Supplier.php | 77 + src/Financial/Entity/Invoice/Bank.php | 61 + src/Financial/Entity/Invoice/Invoice.php | 205 + src/Financial/Entity/Invoice/InvoiceList.php | 45 + src/Financial/Entity/Invoice/Item.php | 137 + src/Financial/Entity/Invoice/ItemIterator.php | 46 + src/Financial/Entity/Invoice/Supplier.php | 45 + src/Financial/Entity/Invoice/Tax.php | 61 + src/Financial/Entity/Invoice/TaxIterator.php | 49 + src/Financial/Entity/Invoice/TaxRecap.php | 45 + src/Financial/Entity/Offset/InvoiceSimple.php | 123 + .../Entity/Offset/InvoiceSimpleIterator.php | 48 + src/Financial/Entity/Offset/Offset.php | 144 + src/Financial/Entity/Offset/OffsetList.php | 45 + src/Financial/Entity/Offset/Order.php | 96 + src/Financial/Entity/Offset/OrderIterator.php | 48 + src/Financial/FinancialClient.php | 75 + src/Label/Entity/Label.php | 45 + src/Label/Entity/LabelIterator.php | 46 + src/Label/LabelClient.php | 19 + src/MpApiClient.php | 107 + src/MpApiClientOptions.php | 91 + src/Order/DTO/ShippingLabelRequest.php | 79 + src/Order/DTO/StatusRequest.php | 103 + src/Order/Entity/BasicOrder.php | 159 + src/Order/Entity/BasicOrderList.php | 48 + src/Order/Entity/Branches.php | 61 + src/Order/Entity/ConsignmentStatus.php | 57 + .../Entity/ConsignmentStatusIterator.php | 46 + src/Order/Entity/Customer.php | 111 + src/Order/Entity/Item.php | 110 + src/Order/Entity/ItemIterator.php | 48 + src/Order/Entity/Order.php | 339 + src/Order/Entity/Stats.php | 93 + src/Order/Entity/StatusEnum.php | 42 + src/Order/Entity/Tracking.php | 58 + src/Order/OrderClient.php | 83 + src/Shop/Entity/Shop.php | 77 + src/Shop/Entity/ShopIdEnum.php | 39 + src/Shop/Entity/ShopIterator.php | 51 + src/Shop/ShopClient.php | 19 + src/SupplyDelay/Entity/SupplyDelay.php | 61 + src/SupplyDelay/SupplyDelayClient.php | 67 + tests/_data/.gitkeep | 0 tests/_output/.gitignore | 2 + tests/_support/AcceptanceTester.php | 27 + tests/_support/FunctionalTester.php | 29 + tests/_support/Helper/Acceptance.php | 13 + tests/_support/Helper/Functional.php | 103 + tests/_support/Helper/Unit.php | 13 + tests/_support/UnitTester.php | 27 + tests/_support/_generated/.gitignore | 2 + tests/acceptance.suite.yml | 14 + tests/acceptance/.gitkeep | 0 tests/functional.suite.yml | 21 + tests/functional/.gitkeep | 0 tests/functional/ArticleClientCest.php | 668 ++ tests/functional/BrandClientCest.php | 39 + tests/functional/CategoryClientCest.php | 152 + tests/functional/ChecksClientCest.php | 66 + tests/functional/FinancialClientCest.php | 278 + tests/functional/LabelClientCest.php | 39 + tests/functional/MpApiClientCest.php | 52 + tests/functional/OrderClientCest.php | 199 + tests/functional/ShopClientCest.php | 44 + tests/functional/SupplyDelayClientCest.php | 108 + tests/functional/bootstrap.php | 301 + tests/unit.suite.yml | 11 + tests/unit/DataTypeUtilTest.php | 144 + tests/unit/FilterItemTest.php | 153 + tests/unit/FilterTest.php | 98 + tests/unit/InputDataUtilTest.php | 99 + tests/unit/JsonSerializeEntityTraitTest.php | 58 + tests/unit/StringEnumAbstractTest.php | 144 + 199 files changed, 20898 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.dist create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 CHANGELOG.md create mode 100644 Dockerfile.php-7_4 create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 codeception.yml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 doc/Article.md create mode 100644 doc/Brand.md create mode 100644 doc/Category.md create mode 100644 doc/Checks.md create mode 100644 doc/Exception.md create mode 100644 doc/Financial.md create mode 100644 doc/Label.md create mode 100644 doc/Order.md create mode 100644 doc/Shop.md create mode 100644 doc/SupplyDelay.md create mode 100644 example/Brand.php create mode 100644 example/Category.php create mode 100644 example/Checks.php create mode 100644 example/Exception.php create mode 100644 example/Financial.php create mode 100644 example/Label.php create mode 100644 example/Order.php create mode 100644 example/Products.php create mode 100644 example/Shop.php create mode 100644 example/SupplyDelay.php create mode 100644 phpcs-ruleset.xml create mode 100644 phpmd.xml create mode 100644 phpstan.neon create mode 100644 psalm.xml create mode 100644 src/Article/ArticleClient.php create mode 100644 src/Article/DTO/AbstractArticleRequest.php create mode 100644 src/Article/DTO/BatchAvailability.php create mode 100644 src/Article/DTO/BatchAvailabilityIterator.php create mode 100644 src/Article/DTO/ProductRequest.php create mode 100644 src/Article/DTO/VariantRequest.php create mode 100644 src/Article/DTO/VariantRequestIterator.php create mode 100644 src/Article/Entity/BasicProduct.php create mode 100644 src/Article/Entity/BasicProductList.php create mode 100644 src/Article/Entity/BasicVariant.php create mode 100644 src/Article/Entity/BasicVariantList.php create mode 100644 src/Article/Entity/Common/AbstractArticle.php create mode 100644 src/Article/Entity/Common/Availability.php create mode 100644 src/Article/Entity/Common/Dimensions.php create mode 100644 src/Article/Entity/Common/Label.php create mode 100644 src/Article/Entity/Common/LabelIterator.php create mode 100644 src/Article/Entity/Common/Media.php create mode 100644 src/Article/Entity/Common/MediaIterator.php create mode 100644 src/Article/Entity/Common/Override.php create mode 100644 src/Article/Entity/Common/OverrideIterator.php create mode 100644 src/Article/Entity/Common/PackageSizeEnum.php create mode 100644 src/Article/Entity/Common/Parameter.php create mode 100644 src/Article/Entity/Common/ParameterIterator.php create mode 100644 src/Article/Entity/Common/Pricing.php create mode 100644 src/Article/Entity/Common/Promotion.php create mode 100644 src/Article/Entity/Common/PromotionIterator.php create mode 100644 src/Article/Entity/Common/StatusEnum.php create mode 100644 src/Article/Entity/Product.php create mode 100644 src/Article/Entity/ProductStageEnum.php create mode 100644 src/Article/Entity/Variant.php create mode 100644 src/Article/Entity/VariantIterator.php create mode 100644 src/Brand/BrandClient.php create mode 100644 src/Brand/Entity/Brand.php create mode 100644 src/Brand/Entity/BrandIterator.php create mode 100644 src/Category/CategoryClient.php create mode 100644 src/Category/Entity/Category.php create mode 100644 src/Category/Entity/CategoryIterator.php create mode 100644 src/Category/Entity/Parameter.php create mode 100644 src/Category/Entity/ParameterIterator.php create mode 100644 src/Category/Entity/ParameterValue.php create mode 100644 src/Category/Entity/ParameterValueIterator.php create mode 100644 src/Category/Entity/TreeItem.php create mode 100644 src/Category/Entity/TreeItemIterator.php create mode 100644 src/Category/Entity/TreeMenuConstraint.php create mode 100644 src/Category/Entity/TreeMenuConstraintIterator.php create mode 100644 src/Category/Entity/TreeMenuItem.php create mode 100644 src/Category/Entity/TreeMenuItemIterator.php create mode 100644 src/Category/Entity/TreeSapCategory.php create mode 100644 src/Category/Entity/TreeSapCategoryIterator.php create mode 100644 src/Checks/ChecksClient.php create mode 100644 src/Checks/Entity/Error.php create mode 100644 src/Checks/Entity/ErrorIterator.php create mode 100644 src/Common/AbstractMpApiClient.php create mode 100644 src/Common/Authenticators/ClientIdAuthenticator.php create mode 100644 src/Common/DTO/Paging.php create mode 100644 src/Common/Interfaces/ArticleClientInterface.php create mode 100644 src/Common/Interfaces/AuthMiddlewareInterface.php create mode 100644 src/Common/Interfaces/BrandClientInterface.php create mode 100644 src/Common/Interfaces/CategoryClientInterface.php create mode 100644 src/Common/Interfaces/ChecksClientInterface.php create mode 100644 src/Common/Interfaces/ClientInterface.php create mode 100644 src/Common/Interfaces/FinancialClientInterface.php create mode 100644 src/Common/Interfaces/LabelClientInterface.php create mode 100644 src/Common/Interfaces/MpApiClientInterface.php create mode 100644 src/Common/Interfaces/OrderClientInterface.php create mode 100644 src/Common/Interfaces/ShopClientInterface.php create mode 100644 src/Common/Interfaces/SupplyDelayClientInterface.php create mode 100644 src/Common/Util/AbstractIntKeyIterator.php create mode 100644 src/Common/Util/AbstractList.php create mode 100644 src/Common/Util/AbstractStringEnum.php create mode 100644 src/Common/Util/AbstractStringKeyIterator.php create mode 100644 src/Common/Util/DataTypeUtil.php create mode 100644 src/Common/Util/InputDataUtil.php create mode 100644 src/Common/Util/JsonSerializeEntityTrait.php create mode 100644 src/Exception/BadResponseException.php create mode 100644 src/Exception/ErrorCode.php create mode 100644 src/Exception/ExceptionFactory.php create mode 100644 src/Exception/IncorrectDataTypeException.php create mode 100644 src/Exception/MpApiException.php create mode 100644 src/Exception/NotFoundException.php create mode 100644 src/Exception/TooManyRequestsException.php create mode 100644 src/Exception/UnauthorizedException.php create mode 100644 src/Filter/Filter.php create mode 100644 src/Filter/FilterItem.php create mode 100644 src/Filter/FilterOperatorEnum.php create mode 100644 src/Financial/Entity/Common/Address.php create mode 100644 src/Financial/Entity/Common/Attachment.php create mode 100644 src/Financial/Entity/Common/Customer.php create mode 100644 src/Financial/Entity/Common/Supplier.php create mode 100644 src/Financial/Entity/Invoice/Bank.php create mode 100644 src/Financial/Entity/Invoice/Invoice.php create mode 100644 src/Financial/Entity/Invoice/InvoiceList.php create mode 100644 src/Financial/Entity/Invoice/Item.php create mode 100644 src/Financial/Entity/Invoice/ItemIterator.php create mode 100644 src/Financial/Entity/Invoice/Supplier.php create mode 100644 src/Financial/Entity/Invoice/Tax.php create mode 100644 src/Financial/Entity/Invoice/TaxIterator.php create mode 100644 src/Financial/Entity/Invoice/TaxRecap.php create mode 100644 src/Financial/Entity/Offset/InvoiceSimple.php create mode 100644 src/Financial/Entity/Offset/InvoiceSimpleIterator.php create mode 100644 src/Financial/Entity/Offset/Offset.php create mode 100644 src/Financial/Entity/Offset/OffsetList.php create mode 100644 src/Financial/Entity/Offset/Order.php create mode 100644 src/Financial/Entity/Offset/OrderIterator.php create mode 100644 src/Financial/FinancialClient.php create mode 100644 src/Label/Entity/Label.php create mode 100644 src/Label/Entity/LabelIterator.php create mode 100644 src/Label/LabelClient.php create mode 100644 src/MpApiClient.php create mode 100644 src/MpApiClientOptions.php create mode 100644 src/Order/DTO/ShippingLabelRequest.php create mode 100644 src/Order/DTO/StatusRequest.php create mode 100644 src/Order/Entity/BasicOrder.php create mode 100644 src/Order/Entity/BasicOrderList.php create mode 100644 src/Order/Entity/Branches.php create mode 100644 src/Order/Entity/ConsignmentStatus.php create mode 100644 src/Order/Entity/ConsignmentStatusIterator.php create mode 100644 src/Order/Entity/Customer.php create mode 100644 src/Order/Entity/Item.php create mode 100644 src/Order/Entity/ItemIterator.php create mode 100644 src/Order/Entity/Order.php create mode 100644 src/Order/Entity/Stats.php create mode 100644 src/Order/Entity/StatusEnum.php create mode 100644 src/Order/Entity/Tracking.php create mode 100644 src/Order/OrderClient.php create mode 100644 src/Shop/Entity/Shop.php create mode 100644 src/Shop/Entity/ShopIdEnum.php create mode 100644 src/Shop/Entity/ShopIterator.php create mode 100644 src/Shop/ShopClient.php create mode 100644 src/SupplyDelay/Entity/SupplyDelay.php create mode 100644 src/SupplyDelay/SupplyDelayClient.php create mode 100644 tests/_data/.gitkeep create mode 100644 tests/_output/.gitignore create mode 100644 tests/_support/AcceptanceTester.php create mode 100644 tests/_support/FunctionalTester.php create mode 100644 tests/_support/Helper/Acceptance.php create mode 100644 tests/_support/Helper/Functional.php create mode 100644 tests/_support/Helper/Unit.php create mode 100644 tests/_support/UnitTester.php create mode 100644 tests/_support/_generated/.gitignore create mode 100644 tests/acceptance.suite.yml create mode 100644 tests/acceptance/.gitkeep create mode 100644 tests/functional.suite.yml create mode 100644 tests/functional/.gitkeep create mode 100644 tests/functional/ArticleClientCest.php create mode 100644 tests/functional/BrandClientCest.php create mode 100644 tests/functional/CategoryClientCest.php create mode 100644 tests/functional/ChecksClientCest.php create mode 100644 tests/functional/FinancialClientCest.php create mode 100644 tests/functional/LabelClientCest.php create mode 100644 tests/functional/MpApiClientCest.php create mode 100644 tests/functional/OrderClientCest.php create mode 100644 tests/functional/ShopClientCest.php create mode 100644 tests/functional/SupplyDelayClientCest.php create mode 100644 tests/functional/bootstrap.php create mode 100644 tests/unit.suite.yml create mode 100644 tests/unit/DataTypeUtilTest.php create mode 100644 tests/unit/FilterItemTest.php create mode 100644 tests/unit/FilterTest.php create mode 100644 tests/unit/InputDataUtilTest.php create mode 100644 tests/unit/JsonSerializeEntityTraitTest.php create mode 100644 tests/unit/StringEnumAbstractTest.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ec55b5d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending for every file +[*] +end_of_line = lf +insert_final_newline = true \ No newline at end of file diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..ec8dcf5 --- /dev/null +++ b/.env.dist @@ -0,0 +1,3 @@ +# Env config for functional tests +CLIENT_ID=client-id-for-tests +API_PATH=https://mpapi.mallgroup.com diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ce7609b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Auto detect text files and perform LF normalization +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c9f6fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.idea +/vendor +/tmp +.env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..3c1c867 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,120 @@ +.cache: + - .: &composer-cache + key: + prefix: composer-cache + files: + - composer.lock + paths: + - vendor/ + policy: pull + +stages: + - init + - lint + - analysis + - testing + +# Install composer only once at the beginning, then only use the cached file +init: + image: registry.mallgroup.com/di/php:7.4-alpine + stage: init + cache: + key: + prefix: composer-cache + files: + - composer.lock + paths: + - vendor/ + script: + - composer2 validate --strict + - composer2 install --prefer-dist --no-ansi --no-interaction + tags: + - docker + +phpmd: + image: registry.mallgroup.com/di/php:7.4-alpine + stage: lint + needs: + - init + cache: + - *composer-cache + script: + - $(pwd)/vendor/bin/phpmd ./src text ./phpmd.xml + tags: + - docker + +CS: + image: registry.mallgroup.com/di/php:7.4-alpine + stage: lint + needs: + - init + cache: + - *composer-cache + script: + - $(pwd)/vendor/bin/phpcs --standard=phpcs-ruleset.xml -d memory_limit=-1 --extensions=php --colors -sp ./src + only: + - branches + tags: + - docker + +phpstan: + image: registry.mallgroup.com/di/php:7.4-alpine + stage: analysis + needs: + - init + cache: + - *composer-cache + - key: phpstan-cache + paths: + - tmp/phpstan + before_script: + - mkdir -p $(pwd)/tmp/phpstan + - touch $(pwd)/.env + script: + - $(pwd)/vendor/bin/codecept build + - $(pwd)/vendor/bin/phpstan analyse -c ./phpstan.neon --no-progress --memory-limit=-1 + only: + - branches + tags: + - docker + +psalm: + image: registry.mallgroup.com/di/php:7.4-alpine + stage: analysis + needs: + - init + cache: + - *composer-cache + script: + - $(pwd)/vendor/bin/psalm + only: + - branches + tags: + - docker + +unit-tests: + image: registry.mallgroup.com/di/php:7.4-extras + stage: testing + cache: + - *composer-cache + script: + - touch $(pwd)/.env # .env file must exist, even if envs are taken from environment and not .env file + - php $(pwd)/vendor/bin/codecept run unit --env dev --no-colors + only: + - branches + tags: + - docker + +coverage: + image: registry.mallgroup.com/di/php:7.4-extras + stage: testing + cache: + - *composer-cache + script: + - touch $(pwd)/.env # .env file must exist, even if envs are taken from environment and not .env file + # run all codeception tests with coverage enabled + - php -d pcov.exclude="~vendor~" $(pwd)/vendor/bin/codecept run --env dev --coverage-xml --phpunit-xml --no-colors + only: + - branches + tags: + - docker diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d556950 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Marketplace API client - change log + +## 4.0.0 +- new major version, incompatible with v3 diff --git a/Dockerfile.php-7_4 b/Dockerfile.php-7_4 new file mode 100644 index 0000000..564c735 --- /dev/null +++ b/Dockerfile.php-7_4 @@ -0,0 +1,21 @@ +FROM composer:2 AS composer + +FROM php:7.4-cli-alpine + +RUN apk add --no-cache \ + bash~=5.1 \ + # install build dependencies + && apk add --no-cache --virtual \ + build_deps \ + autoconf~=2 \ + build-base~=0.5 \ + # install PCOV + && pecl install pcov-1.0.9 \ + && docker-php-ext-enable pcov \ + # create user to run as + && adduser -D php + +# install composer +COPY --from=composer --chown=php /usr/bin/composer /usr/local/bin/composer + +USER php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f33f341 --- /dev/null +++ b/Makefile @@ -0,0 +1,88 @@ +include .env + +MOUNT_PATH=/tmp/client +DOCKER_IMAGE=ghcr.io/mallgroup/mpapi-client-php/php:7.4-alpine # TODO: `docker build -t "ghcr.io/mallgroup/mpapi-client-php/php:7.4-alpine" -f Dockerfile.php-7_4 .` +DOCKER_RUN=docker run -it --rm -w $(MOUNT_PATH) -v ${PWD}:$(MOUNT_PATH) $(DOCKER_IMAGE) + +help: + @echo "General" + @echo " init - Initialize client" + @echo " shell - Run interactive shell" + @echo "" + @echo "Composer" + @echo " composer [args] - Run custom composer command" + @echo " composer-install - Install composer dependencies" + @echo " composer-update - Update composer dependencies" + @echo " composer-validate - Validate composer json and lock files" + @echo "" + @echo "Code style and quality" + @echo " fix - Run all automatic code fixers" + @echo " lint - Run all linters" + @echo " lint-full - Run all linters and code coverage" + @echo " phpcs [args] - Run PHP Code Sniffer" + @echo " phpcbf [args] - Run PHP Code Beautifier and Fixer" + @echo " phpmd - Run PHP Mess Detector" + @echo " phpstan - Run PHPStan" + @echo " psalm - Run Psalm" + @echo " coverage - Get php code coverage" + @echo "" + @echo "Tests" + @echo " test [args] - Run all tests (default, or custom test using ARGS=\"tests/unit/FilterTest.php\")" + @echo " test-unit [args] - Run unit tests" + @echo " test-functional [args] - Run functional tests" + +.env: + cat .env.dist >> .env; + +init: composer-install codecept-build + +shell: + $(DOCKER_RUN) bash + +composer: + $(DOCKER_RUN) composer $(ARGS) + +composer-install: + make composer ARGS="install" + +composer-update: + make composer ARGS="update" + +composer-validate: + make composer ARGS="validate --strict" + +codecept-build: + $(DOCKER_RUN) ./vendor/bin/codecept build + +fix: phpcbf + +lint: composer-validate phpcs phpmd phpstan psalm + +lint-full: init lint coverage + +phpcbf: + $(DOCKER_RUN) ./vendor/bin/phpcbf --standard=./phpcs-ruleset.xml -d memory_limit=-1 --extensions=php --colors $(ARGS) -sp ./src + +phpcs: + $(DOCKER_RUN) ./vendor/bin/phpcs --standard=./phpcs-ruleset.xml -d memory_limit=-1 --extensions=php --colors $(ARGS) -sp ./src + +phpmd: + $(DOCKER_RUN) ./vendor/bin/phpmd ./src text ./phpmd.xml + +phpstan: + $(DOCKER_RUN) ./vendor/bin/phpstan analyse -c ./phpstan.neon --memory-limit=-1 + +psalm: + $(DOCKER_RUN) ./vendor/bin/psalm + +coverage: + $(DOCKER_RUN) php -d pcov.exclude="~vendor~" ./vendor/bin/codecept run --coverage-text --coverage-html --phpunit-xml + +test: + $(DOCKER_RUN) ./vendor/bin/codecept run $(ARGS) --env local + +test-unit: + $(DOCKER_RUN) ./vendor/bin/codecept run unit $(ARGS) --env local + +test-functional: + $(DOCKER_RUN) ./vendor/bin/codecept run functional $(ARGS) --env local diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b056be --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +![License](https://img.shields.io/badge/license-Apache_2-blue) +![PHPStan](https://img.shields.io/badge/PHPStan-level%20Max-brightgreen.svg?style=flat&logo=php) +![Psalm](https://img.shields.io/badge/Psalm-level%202-brightgreen.svg?style=flat&logo=php) + +# Mall Marketplace API Client + +## Description + +MPAPI client is a tool created to help Internet Mall, a. s. partners easily manage article catalogue, deliveries, orders etc. using Mall Marketplace API. + +## Installation + +To install the client using [Composer](https://getcomposer.org/doc/00-intro.md) run following command in your repository + +```console +composer require mallgroup/mpapi-client +``` + +## Implementation + +### Info + +Client consists of one main client and multiple, separate, domain clients. + +The main client groups all domain clients under one object, for easier implementation, but every domain client can be initialized and used by itself. + +Every client provides an interface that SHOULD be used as parameter types in code, instead of client classes themselves (i.e., use `MpApiClientInterface $client` +or `BrandsClientInterface $client` instead of `MpApiClient $client` or `BrandsClient $client`). + +When initializing the client, you MUST provide + +1. an authenticator implementing [AuthMiddlewareInterface](src/Common/Interfaces/AuthMiddlewareInterface.php) + - currently, only [ClientIdAuthenticator](src/Common/Authenticators/ClientIdAuthenticator.php), which accepts `my-client-id`, is provided + - in the future, new authenticators will be released (i.e., OAuth) +2. name of the app using the API + - it is sent with every request to Mall API for easier request identification and debugging of reported issues + - please provide a simple, yet meaningful name, i.e., `MyAppName CRM` or `MyAppName Order sync` instead of a random string + +### Examples + +There are 2 main ways to initialize the client + +### 1. Using [MpApiClient](src/MpApiClient.php) with default config + +```php +brand(); +``` + +### 2. Using [MpApiClient](src/MpApiClient.php) (or any other domain client) with custom http client + +- every domain client can be initialized this way as a standalone client +- when initializing a custom http client, `handler` stack with `AuthMiddlewareInterface` MUST be provided! + +```php +push($authenticator->getHandler()); + +$httpClient = new Client( + [ + 'base_uri' => 'https://mpapi.mallgroup.com/v1/', + 'timeout' => 10, + 'allow_redirects' => true, + 'handler' => $handlerStack, + ] +); + +// 2.2. Create Guzzle client using MpApiClientOptions object +$options = new MpApiClientOptions($authenticator); +$options->setTimeout(30); + +$httpClient = new Client($options->getGuzzleOptionsArray()); + +// 3. Create MpApiClient and BrandClient using custom Guzzle client +$client = new MpApiClient($httpClient, 'my-app-name') +$brandClient = new BrandClient($httpClient, 'my-app-name') +``` + +## Examples for all client domains + +* [Article](doc/Article.md) +* [Brand](doc/Brand.md) +* [Category](doc/Category.md) +* [Checks](doc/Checks.md) +* [Financial](doc/Financial.md) +* [Label](doc/Label.md) +* [Order](doc/Order.md) +* [Shop](doc/Shop.md) +* [SupplyDelay](doc/SupplyDelay.md) + +List of custom Exceptions thrown in this client can be found [here](doc/Exception.md) + +## ⚠ Warning + +- client does not include support for deprecated endpoints (i.e., deliveries or gifts), that will be changed, replaced or removed in the future + +### This is still a Beta release, and some documentation and examples are missing or incomplete + +- [ ] Article client documentation +- [ ] Order client documentation and examples diff --git a/codeception.yml b/codeception.yml new file mode 100644 index 0000000..0d8678e --- /dev/null +++ b/codeception.yml @@ -0,0 +1,22 @@ +namespace: MpApiClient\Tests +paths: + tests: tests + output: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +actor_suffix: +settings: + colors: true + error_level: E_ALL + memory_limit: 1024M +extensions: + enabled: + - Codeception\Extension\RunFailed +coverage: + enabled: true + include: + - src/* +params: + - .env + - env # prefer environment values over .env file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3e95896 --- /dev/null +++ b/composer.json @@ -0,0 +1,45 @@ +{ + "name": "mallgroup/mpapi-client-php", + "description": "PHP Client for Mall marketplace API", + "license": "Apache-2.0", + "type": "library", + "authors": [ + { + "name": "Internet Mall, a.s." + } + ], + "support": { + "docs": "https://marketplaceapiv2.docs.apiary.io/", + "wiki": "https://knowledgebase.mallgroup.com/" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "MpApiClient\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "MpApiClient\\Tests\\": "tests/" + } + }, + "require": { + "php-64bit": "^7.4 || ^8.0", + "guzzlehttp/guzzle": "^7.0", + "ext-json": "*" + }, + "require-dev": { + "codeception/codeception": "^4.1", + "codeception/module-asserts": "^1.0.0", + "codeception/module-phpbrowser": "^1.0.0", + "phpmd/phpmd": "^2.10", + "phpstan/phpstan": "^0.12.88", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.7", + "vlucas/phpdotenv": "^5.3" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2ef7352 --- /dev/null +++ b/composer.lock @@ -0,0 +1,6107 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "cc97051488b392bd43eafaea99806292", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "7.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7008573787b430c1c1f650e3722d9bba59967628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.3-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2021-03-23T11:33:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.1" + }, + "time": "2021-03-07T09:25:29+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.8.2" + }, + "time": "2021-04-26T09:17:50+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-01-10T17:06:37+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "behat/gherkin", + "version": "v4.8.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2391482cd003dfdc36b679b27e9f5326bd656acd", + "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd", + "shasum": "" + }, + "require": { + "php": "~7.2|~8.0" + }, + "require-dev": { + "cucumber/cucumber": "dev-gherkin-16.0.0", + "phpunit/phpunit": "~8|~9", + "symfony/phpunit-bridge": "~3|~4|~5", + "symfony/yaml": "~3|~4|~5" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "support": { + "issues": "https://github.com/Behat/Gherkin/issues", + "source": "https://github.com/Behat/Gherkin/tree/v4.8.0" + }, + "time": "2021-02-04T12:44:21+00:00" + }, + { + "name": "codeception/codeception", + "version": "4.1.21", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Codeception.git", + "reference": "c25f20d842a7e3fa0a8e6abf0828f102c914d419" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c25f20d842a7e3fa0a8e6abf0828f102c914d419", + "reference": "c25f20d842a7e3fa0a8e6abf0828f102c914d419", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.4.0", + "codeception/lib-asserts": "^1.0", + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0", + "codeception/stub": "^2.0 | ^3.0", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "~1.4", + "php": ">=5.6.0 <9.0", + "symfony/console": ">=2.7 <6.0", + "symfony/css-selector": ">=2.7 <6.0", + "symfony/event-dispatcher": ">=2.7 <6.0", + "symfony/finder": ">=2.7 <6.0", + "symfony/yaml": ">=2.7 <6.0" + }, + "require-dev": { + "codeception/module-asserts": "1.*@dev", + "codeception/module-cli": "1.*@dev", + "codeception/module-db": "1.*@dev", + "codeception/module-filesystem": "1.*@dev", + "codeception/module-phpbrowser": "1.*@dev", + "codeception/specify": "~0.3", + "codeception/util-universalframework": "*@dev", + "monolog/monolog": "~1.8", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <6.0", + "vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0 | ^5.0" + }, + "suggest": { + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "hoa/console": "For interactive console functionality", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" + }, + "bin": [ + "codecept" + ], + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Codeception\\": "src/Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "http://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "support": { + "issues": "https://github.com/Codeception/Codeception/issues", + "source": "https://github.com/Codeception/Codeception/tree/4.1.21" + }, + "funding": [ + { + "url": "https://opencollective.com/codeception", + "type": "open_collective" + } + ], + "time": "2021-05-28T17:43:39+00:00" + }, + { + "name": "codeception/lib-asserts", + "version": "1.13.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/lib-asserts.git", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/184231d5eab66bc69afd6b9429344d80c67a33b6", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6", + "shasum": "" + }, + "require": { + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3 | ^9.0", + "ext-dom": "*", + "php": ">=5.6.0 <9.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Assertion methods used by Codeception core and Asserts module", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/lib-asserts/issues", + "source": "https://github.com/Codeception/lib-asserts/tree/1.13.2" + }, + "time": "2020-10-21T16:26:20+00:00" + }, + { + "name": "codeception/lib-innerbrowser", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/lib-innerbrowser.git", + "reference": "4b0d89b37fe454e060a610a85280a87ab4f534f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/4b0d89b37fe454e060a610a85280a87ab4f534f1", + "reference": "4b0d89b37fe454e060a610a85280a87ab4f534f1", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.6.0 <9.0", + "symfony/browser-kit": ">=2.7 <6.0", + "symfony/dom-crawler": ">=2.7 <6.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "require-dev": { + "codeception/util-universalframework": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Parent library for all Codeception framework modules and PhpBrowser", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/lib-innerbrowser/issues", + "source": "https://github.com/Codeception/lib-innerbrowser/tree/1.5.0" + }, + "time": "2021-04-23T06:18:29+00:00" + }, + { + "name": "codeception/module-asserts", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-asserts.git", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/59374f2fef0cabb9e8ddb53277e85cdca74328de", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de", + "shasum": "" + }, + "require": { + "codeception/codeception": "*@dev", + "codeception/lib-asserts": "^1.13.1", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Codeception module containing various assertions", + "homepage": "https://codeception.com/", + "keywords": [ + "assertions", + "asserts", + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/module-asserts/issues", + "source": "https://github.com/Codeception/module-asserts/tree/1.3.1" + }, + "time": "2020-10-21T16:48:15+00:00" + }, + { + "name": "codeception/module-phpbrowser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/module-phpbrowser.git", + "reference": "770a6be4160a5c0c08d100dd51bff35f6056bbf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/module-phpbrowser/zipball/770a6be4160a5c0c08d100dd51bff35f6056bbf1", + "reference": "770a6be4160a5c0c08d100dd51bff35f6056bbf1", + "shasum": "" + }, + "require": { + "codeception/codeception": "^4.0", + "codeception/lib-innerbrowser": "^1.3", + "guzzlehttp/guzzle": "^6.3|^7.0", + "php": ">=5.6.0 <9.0" + }, + "conflict": { + "codeception/codeception": "<4.0" + }, + "require-dev": { + "codeception/module-rest": "^1.0" + }, + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + } + ], + "description": "Codeception module for testing web application over HTTP", + "homepage": "http://codeception.com/", + "keywords": [ + "codeception", + "functional-testing", + "http" + ], + "support": { + "issues": "https://github.com/Codeception/module-phpbrowser/issues", + "source": "https://github.com/Codeception/module-phpbrowser/tree/1.0.2" + }, + "time": "2020-10-24T15:29:28+00:00" + }, + { + "name": "codeception/phpunit-wrapper", + "version": "9.0.6", + "source": { + "type": "git", + "url": "https://github.com/Codeception/phpunit-wrapper.git", + "reference": "b0c06abb3181eedca690170f7ed0fd26a70bfacc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/b0c06abb3181eedca690170f7ed0fd26a70bfacc", + "reference": "b0c06abb3181eedca690170f7ed0fd26a70bfacc", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "phpunit/phpunit": "^9.0" + }, + "require-dev": { + "codeception/specify": "*", + "consolidation/robo": "^3.0.0-alpha3", + "vlucas/phpdotenv": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\PHPUnit\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + }, + { + "name": "Naktibalda" + } + ], + "description": "PHPUnit classes used by Codeception", + "support": { + "issues": "https://github.com/Codeception/phpunit-wrapper/issues", + "source": "https://github.com/Codeception/phpunit-wrapper/tree/9.0.6" + }, + "time": "2020-12-28T13:59:47+00:00" + }, + { + "name": "codeception/stub", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "468dd5fe659f131fc997f5196aad87512f9b1304" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/468dd5fe659f131fc997f5196aad87512f9b1304", + "reference": "468dd5fe659f131fc997f5196aad87512f9b1304", + "shasum": "" + }, + "require": { + "phpunit/phpunit": "^8.4 | ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "support": { + "issues": "https://github.com/Codeception/Stub/issues", + "source": "https://github.com/Codeception/Stub/tree/3.7.0" + }, + "time": "2020-07-03T15:54:43+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.2", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-05-24T07:46:03+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.5", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-05-24T12:41:47+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", + "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/2.0.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-05-05T19:37:51+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.1", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2020-12-07T18:04:37+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" + }, + "time": "2021-02-22T14:02:09+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "phpoption/phpoption": "^1.7.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2020-04-13T13:17:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + }, + "time": "2020-12-01T19:48:11+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + }, + "time": "2021-05-03T19:11:20+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "1632f0cee84512ffd6dde71e58536b3b06528c41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/1632f0cee84512ffd6dde71e58536b3b06528c41", + "reference": "1632f0cee84512ffd6dde71e58536b3b06528c41", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5", + "symfony/filesystem": "^2.3.0|^3|^4|^5" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "phpunit/phpunit": "^4.8.36|^5.7.27", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.9.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2021-04-15T21:36:28+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "bd5ef43d1dcaf7272605027c959c1c5ff3761f7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/bd5ef43d1dcaf7272605027c959c1c5ff3761f7a", + "reference": "bd5ef43d1dcaf7272605027c959c1c5ff3761f7a", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.9.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "phpunit/phpunit": "^4.8.36 || ^5.7.27", + "squizlabs/php_codesniffer": "^2.0" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.10.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2021-05-11T17:16:16+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.7.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2020-07-20T17:29:33+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + }, + "time": "2021-03-17T13:42:18+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.5.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "e352d065af1ae9b41c12d1dfd309e90f7b1f55c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e352d065af1ae9b41c12d1dfd309e90f7b1f55c9", + "reference": "e352d065af1ae9b41c12d1dfd309e90f7b1f55c9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.60", + "phpstan/phpstan-strict-rules": "^0.12.5", + "phpunit/phpunit": "^7.5.20", + "symfony/process": "^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.4" + }, + "time": "2021-04-03T14:46:19+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "0.12.90", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "f0e4b56630fc3d4eb5be86606d07212ac212ede4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f0e4b56630fc3d4eb5be86606d07212ac212ede4", + "reference": "f0e4b56630fc3d4eb5be86606d07212ac212ede4", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/0.12.90" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2021-06-18T07:15:38+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f6293e1b30a2354e8428e004689671b83871edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-28T07:26:59+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", + "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.6" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-23T05:14:38+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.0.9", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "d59652e000bcde019459dcba677de030867d0232" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/d59652e000bcde019459dcba677de030867d0232", + "reference": "d59652e000bcde019459dcba677de030867d0232", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.5.1 - 0.5.4", + "squizlabs/php_codesniffer": "^3.6.0" + }, + "require-dev": { + "phing/phing": "2.16.4", + "php-parallel-lint/php-parallel-lint": "1.3.0", + "phpstan/phpstan": "0.12.88", + "phpstan/phpstan-deprecation-rules": "0.12.6", + "phpstan/phpstan-phpunit": "0.12.19", + "phpstan/phpstan-strict-rules": "0.12.9", + "phpunit/phpunit": "7.5.20|8.5.5|9.5.5" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.0.9" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2021-06-07T10:08:42+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-04-09T00:54:41+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "379984e25eee9811b0a25a2105e1a2b3b8d9b734" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/379984e25eee9811b0a25a2105e1a2b3b8d9b734", + "reference": "379984e25eee9811b0a25a2105e1a2b3b8d9b734", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/dom-crawler": "^4.4|^5.0" + }, + "require-dev": { + "symfony/css-selector": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/config", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "9c307728cfacbd50914f0db28aee1e0ecd82b99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/9c307728cfacbd50914f0db28aee1e0ecd82b99f", + "reference": "9c307728cfacbd50914f0db28aee1e0ecd82b99f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/filesystem": "^4.4|^5.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php81": "^1.22" + }, + "conflict": { + "symfony/finder": "<4.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/messenger": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-12T10:15:17+00:00" + }, + { + "name": "symfony/console", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-12T09:42:48+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:40:38+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "ddbff73bc4fa3d5b415431d486770ab0e72f6516" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ddbff73bc4fa3d5b415431d486770ab0e72f6516", + "reference": "ddbff73bc4fa3d5b415431d486770ab0e72f6516", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1.6|^2" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4" + }, + "provide": { + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/config": "^5.3", + "symfony/expression-language": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-12T09:17:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "55fff62b19f413f897a752488ade1bc9c8a19cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/55fff62b19f413f897a752488ade1bc9c8a19cdd", + "reference": "55fff62b19f413f897a752488ade1bc9c8a19cdd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^4.4|^5.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/error-handler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "348116319d7fb7d1faa781d26a48922428013eb2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/348116319d7fb7d1faa781d26a48922428013eb2", + "reference": "348116319d7fb7d1faa781d26a48922428013eb2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T12:52:38+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:27:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "e66119f3de95efc359483f810c4c3e6436279436" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", + "reference": "e66119f3de95efc359483f810c4c3e6436279436", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-21T13:25:03+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-04-01T10:43:52+00:00" + }, + { + "name": "symfony/string", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "0732e97e41c0a590f77e231afc16a327375d50b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0", + "reference": "0732e97e41c0a590f77e231afc16a327375d50b0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-06T09:51:56+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "71719ab2409401711d619765aa255f9d352a59b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/71719ab2409401711d619765aa255f9d352a59b2", + "reference": "71719ab2409401711d619765aa255f9d352a59b2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<4.4" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-06T09:51:56+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.8.1", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "f73f2299dbc59a3e6c4d66cff4605176e728ee69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/f73f2299dbc59a3e6c4d66cff4605176e728ee69", + "reference": "f73f2299dbc59a3e6c4d66cff4605176e728ee69", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1 || ^2.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.5", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.10.5", + "openlss/lib-array2xml": "^1.0", + "php": "^7.1|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0||^6.0", + "ext-curl": "*", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0||dev-master", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.16", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3 || ^5.0", + "weirdan/phpunit-appveyor-reporter": "^1.0.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-igbinary": "^2.0.5" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + }, + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.8.1" + }, + "time": "2021-06-20T23:03:20+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.1", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.7.4", + "symfony/polyfill-ctype": "^1.17", + "symfony/polyfill-mbstring": "^1.17", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "homepage": "https://gjcampbell.co.uk/" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://vancelucas.com/" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2021-01-20T15:23:13+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php-64bit": "^7.4 || ^8.0", + "ext-json": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/doc/Article.md b/doc/Article.md new file mode 100644 index 0000000..cc28370 --- /dev/null +++ b/doc/Article.md @@ -0,0 +1,9 @@ +# ArticleClient + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## TODO: add documentation + +### See more examples [here](../example/Products.php) diff --git a/doc/Brand.md b/doc/Brand.md new file mode 100644 index 0000000..0b1c130 --- /dev/null +++ b/doc/Brand.md @@ -0,0 +1,39 @@ +# BrandClient + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get list of all brands + +Method returns [BrandIterator](../src/Brand/Entity/BrandIterator.php) containing [Brand](../src/Brand/Entity/Brand.php) entity. + +```php +use MpApiClient\Common\Interfaces\BrandClientInterface; + +/** @var BrandClientInterface $brandClient */ +$brands = $brandClient->list(); +echo json_encode($brands, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "brand_id": "3M", + "title": "3M" + }, + { + "brand_id": "AKASA", + "title": "Akasa" + }, + { + "brandId": "ZYXEL", + "title": "Zyxel" + }, + ... +] +``` + +### See more examples [here](../example/Brand.php) diff --git a/doc/Category.md b/doc/Category.md new file mode 100644 index 0000000..6969942 --- /dev/null +++ b/doc/Category.md @@ -0,0 +1,167 @@ +# Category client + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get list of all categories + +Method returns [CategoryIterator](../src/Category/Entity/CategoryIterator.php) containing [Category](../src/Category/Entity/Category.php) entity. + +```php +use MpApiClient\Common\Interfaces\CategoryClientInterface; + +/** @var CategoryClientInterface $categoryClient */ +$categories = $categoryClient->list(); +echo json_encode($categories, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "category_id": "BB001", + "title": "Big Brands - ostatní" + }, + { + "category_id": "EA001", + "title": "Kuchyňské baterie" + }, + { + "category_id": "PR007", + "title": "Doplňky" + }, + ... +] +``` + +## Get parameters for specific category + +Method returns [CategoryParameterIterator](../src/Category/Entity/ParameterIterator.php) containing [CategoryParameter](../src/Category/Entity/Parameter.php) entity. + +```php +use MpApiClient\Common\Interfaces\CategoryClientInterface; + +/** @var CategoryClientInterface $categoryClient */ +$categoryParams = $categoryClient->getParameters('EA001'); +echo json_encode($categoryParams, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "categoryParamId": "SURFACE", + "title": "Povrch", + "unit": "", + "values": [ + { + "id": "BRON ELOX", + "text": "bronzový elox" + }, + { + "id": "CHROME", + "text": "chrom" + }, + ... + ] + }, + { + "categoryParamId": "LEVER", + "title": "Páková", + "unit": "", + "values": [ + { + "id": "YES", + "text": "Ano" + }, + { + "id": "NO", + "text": "Ne" + }, + { + "id": "N\/A", + "text": "Výrobce neuvádí" + } + ] + }, + { + "categoryParamId": "LENGTH_SHOWER", + "title": "Délka sprchy", + "unit": "cm", + "values": [] + }, + ... +] +``` + +## Get entire mall category tree + +Method expects [ShopIdEnum](../src/Shop/Entity/ShopIdEnum.php) and returns [CategoryTreeItemIterator](../src/Category/Entity/TreeItemIterator.php) +containing [CategoryTreeItem](../src/Category/Entity/TreeItem.php). + +```php +use MpApiClient\Common\Interfaces\CategoryClientInterface; +use MpApiClient\Shop\Entity\ShopIdEnum; + +/** @var CategoryClientInterface $categoryClient */ +$categoryTree = $categoryClient->tree(ShopIdEnum::CZ10MA()); +echo json_encode($categoryTree, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "title": "MALL" + "categoryVisible": true, + "items": [ + { + "title": "Potraviny a nápoje", + "categoryVisible": true, + "items": [ + { + "title": "Dárkové koše", + "categoryVisible": true, + "items": [], + "menuItems": [ + { + "menuItemId": 100058812, + "title": "Pro děti", + "categoryVisible": false, + "sapCategories": [ + { + "operator": "AND", + "menuConstraints": [ + { + "paramId": "MEN_WOMEN_AND", + "operator": "=", + "value1": "pro děti", + "value2": null, + "class": 3 + } + ], + "productTypeId": "NK184", + "segment": "MP" + } + ], + "url": "https://www.mall.cz/darkove-kose-pro-deti" + }, + ... + ] + }, + ... + ], + "menuItems": [] + } + ... + ], + "menuItems": [] + } +] +``` + +### See more examples [here](../example/Category.php) diff --git a/doc/Checks.md b/doc/Checks.md new file mode 100644 index 0000000..21ba43b --- /dev/null +++ b/doc/Checks.md @@ -0,0 +1,91 @@ +# Checks client + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get delivery errors + +Method returns [CheckErrorIterator](../src/Checks/Entity/ErrorIterator.php) containing [CheckError](../src/Checks/Entity/Error.php) entity. + +```php +use MpApiClient\Common\Interfaces\ChecksClientInterface; + +/** @var ChecksClientInterface $checksClient */ +$deliveryErrors = $checksClient->getDeliveryErrors(); +echo json_encode($deliveryErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "code": "MISSING_PACKAGE_DELIVERY", + "attribute": "package_size", + "value": "smallbox", + "msg": "There is no delivery for 'smallbox' package size.", + "articles": [ + "1047" + ] + } +] +``` + +## Get media errors + +Method returns [CheckErrorIterator](../src/Checks/Entity/ErrorIterator.php) containing [CheckError](../src/Checks/Entity/Error.php) entity. + +```php +use MpApiClient\Common\Interfaces\ChecksClientInterface; + +/** @var ChecksClientInterface $checksClient */ +$mediaErrors = $checksClient->getMediaErrors(); +echo json_encode($mediaErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "code": "MEDIA_VALIDATION_ERROR", + "attribute": "media", + "value": "https:\/\/cdn.my-company.cz\/non-existing-image\/large.jpg", + "msg": "cURL error 28: Operation timed out after 0 milliseconds with 0 out of 0 bytes received (see http:\/\/curl.haxx.se\/libcurl\/c\/libcurl-errors.html)", + "articles": [ + "0180869" + ] + }, + { + "code": "MEDIA_VALIDATION_ERROR", + "attribute": "media", + "value": "https:\/\/cdn.my-company.cz\/image-with-big-dimensions\/large.jpg", + "msg": "Sent media exceeded allowed value of 2000px. Your dimensions for media are 2480px x 945px.", + "articles": [ + "0234030" + ] + }, + { + "code": "MEDIA_VALIDATION_ERROR", + "attribute": "media", + "value": "https:\/\/cdn.my-company.cz\/very-large-image\/large.jpg", + "msg": "Image size exceeded. Allowed image size is 2MB.", + "articles": [ + "0234030" + ] + }, + { + "code": "MEDIA_VALIDATION_ERROR", + "attribute": "media", + "value": "https:\/\/cdn.my-company.cz\/unsupported-image\/large.jpg", + "msg": "Unsupported mime type", + "articles": [ + "0234030" + ] + }, + ... +] +``` + +### See more examples [here](../example/Checks.php) diff --git a/doc/Exception.md b/doc/Exception.md new file mode 100644 index 0000000..f1271c0 --- /dev/null +++ b/doc/Exception.md @@ -0,0 +1,66 @@ +# Exceptions + +Client contains custom exceptions, for easier error handling in your application. + +All custom exceptions extend generic `MpApiException`. + +Some methods might throw other native PHP exceptions (i.e., `InvalidArgumentException`), which do not extend `MpApiException`. Such methods always have +appropriate `@throws` tags. + +## [MpApiException](../src/Exception/MpApiException.php) + +- Generic exception which almost all exceptions thrown in this client extend + +## [IncorrectDataTypeException](../src/Exception/IncorrectDataTypeException.php) + +- Thrown when method called expects data of a specific type, but received a different one + +## [BadResponseException](../src/Exception/BadResponseException.php) + +- Thrown when API responded with 4xx or 5xx status code that could not be translated to more specific exceptions +- Usually indicates an unknown/unexpected error occurred + +## [NotFoundException](../src/Exception/NotFoundException.php) + +- Thrown when API returns 404 status code +- This exception should never be thrown for endpoints with static url (i.e., lists) + +## [UnauthorizedException](../src/Exception/UnauthorizedException.php) + +- Thrown when API returns 401 status code +- Reasons might include + - you forgot to provide authenticator middleware + - you provided invalid credentials to authenticator middleware + - disabled user account + +## [TooManyRequestsException](../src/Exception/TooManyRequestsException.php) + +- Thrown when API returns 429 status code +- In that case, you have been rate limited by the API and should slow down your request rate +- API returns rate limit information as part of response headers, which you can easily check + +### Example of handling some errors + +```php +getDeliveryErrors(); +} catch (TooManyRequestsException $e) { + var_dump($e->getResponse()->getHeaders()); +} catch (BadResponseException $e) { + var_dump($e->getErrorCodes()); +} catch (IncorrectDataTypeException $e) { + var_dump('Incorrect type: ' . $e->getMessage()); +} catch (MpApiException $e) { + var_dump('Generic exception: ' . $e->getMessage()); +} +``` + +### See more examples [here](../example/Exception.php) diff --git a/doc/Financial.md b/doc/Financial.md new file mode 100644 index 0000000..b366dd9 --- /dev/null +++ b/doc/Financial.md @@ -0,0 +1,320 @@ +# FinancialClient + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get list of all invoices + +Method returns [InvoiceList](../src/Financial/Entity/Invoice/InvoiceList.php) containing [Invoice](../src/Financial/Entity/Invoice/Invoice.php) entity. + +```php +use MpApiClient\Common\Interfaces\FinancialClientInterface; + +/** @var FinancialClientInterface $financialClient */ +$labels = $financialClient->listInvoices(null); +echo json_encode($labels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "paging": { + "total": 10, + "pages": 1, + "size": 10, + "page": 1 + }, + "data": [ + { + "invoiceNumber": 99991111, + "partner": "3000", + "createdAt": "2018-11-23 00:00:00", + "deliveryAt": "2018-11-26 00:00:00", + "dueDate": "2018-11-26 00:00:00", + "originDocumentId": null, + "currency": "CZK", + "supplier": { + "bank": { + "bankName": "Česká spořitelna", + ... + } + }, + "customer": { + "name": "Internet Mall s.r.o", + ... + "address": { + "street": "U Garáží 1", + "city": "Praha", + "zip": "17001", + "country": "CZ" + } + }, + "items": [ + { + "id": "IDP29826", + ... + } + ], + "filePath": "3000\/attachment", + "total": 1000, + "taxRecap": { + "total": 200, + "taxes": [ + { + "tax": "15", + "base": 190, + "total": 200, + "price": 19 + }, + ... + ] + }, + "note": "", + "purchNoC": "", + "invoiceType": "SB", + "invoiceIndicator": "I", + "documentType": "invoice", + "invoiceTypeTag": "SB" + }, + ... + ] +} +``` + +## Get invoice detail + +Method returns [Invoice](../src/Financial/Entity/Invoice/Invoice.php) entity. + +```php +use MpApiClient\Common\Interfaces\FinancialClientInterface; + +/** @var FinancialClientInterface $financialClient */ +$labels = $financialClient->getInvoice('invoice-id'); +echo json_encode($labels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "invoiceNumber": 99991111, + "partner": "3000", + "createdAt": "2018-11-23 00:00:00", + "deliveryAt": "2018-11-26 00:00:00", + "dueDate": "2018-11-26 00:00:00", + "originDocumentId": null, + "currency": "CZK", + "supplier": { + "bank": { + "bankName": "Česká spořitelna", + ... + } + }, + "customer": { + "name": "Internet Mall s.r.o", + ... + "address": { + "street": "U Garáží 1", + "city": "Praha", + "zip": "17001", + "country": "CZ" + } + }, + "items": [ + { + "id": "IDP29826", + ... + } + ], + "filePath": "3000\/attachment", + "total": 1000, + "taxRecap": { + "total": 200, + "taxes": [ + { + "tax": "15", + "base": 190, + "total": 200, + "price": 19 + }, + ... + ] + }, + "note": "", + "purchNoC": "", + "invoiceType": "SB", + "invoiceIndicator": "I", + "documentType": "invoice", + "invoiceTypeTag": "SB" +} +``` + +## Download invoice + +Method returns [Psr\Http\Message\ResponseInterface](https://www.php-fig.org/psr/psr-7/). + +```php +use MpApiClient\Common\Interfaces\FinancialClientInterface; + +/** @var FinancialClientInterface $financialClient */ +$response = $financialClient->downloadInvoice('invoice-id'); + +header('Content-Type: ' . $response->getHeaderLine('Content-Type')); +echo $response->getBody()->getContents(); +``` + +Example above displays invoice attachment in a browser. + +## Get list of all offsets + +Method returns [OffsetList](../src/Financial/Entity/Offset/OffsetList.php) containing [Offset](../src/Financial/Entity/Offset/Offset.php) entity. + +```php +use MpApiClient\Common\Interfaces\FinancialClientInterface; + +/** @var FinancialClientInterface $financialClient */ +$labels = $financialClient->listOffsets(null); +echo json_encode($labels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "paging": { + "total": 1, + "pages": 1, + "size": 1, + "page": 1 + }, + "data": [ + { + "partner": "3000", + "documentNumber": "0000003000-28032019", + "createdAt": "2019-03-28 00:00:00", + "dueDate": "2018-12-07 00:00:00", + "currency": "CZK", + "diffPrice": -2550.91, + "variableSymbol": 3003190328, + "supplier": { + "name": "VIVANTIS a.s.", + ... + "address": { + "street": "Školní náměstí 14", + "city": "Chrudim", + "zip": "537 01", + "country": "CZ" + } + }, + "customer": { + "name": "Internet Mall, a.s.", + ... + "address": { + "street": "U garáží 1611\/1", + "city": "Praha 7 - Holešovice", + "zip": "170 00", + "country": "CZ" + } + }, + "invoices": [ + { + "id": 3003000034, + ... + } + ], + "orders": [ + { + "id": 10016693501, + ... + } + ], + "attachment": { + "filename": "MP_offset_0000003000-28032019.PDF", + "mime": "application\/pdf" + } + } + ] +} +``` + +## Get offset detail + +Method returns [Offset](../src/Financial/Entity/Offset/Offset.php) entity. + +```php +use MpApiClient\Common\Interfaces\FinancialClientInterface; + +/** @var FinancialClientInterface $financialClient */ +$labels = $financialClient->getOffset('offset-id'); +echo json_encode($labels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "partner": "3000", + "documentNumber": "0000003000-28032019", + "createdAt": "2019-03-28 00:00:00", + "dueDate": "2018-12-07 00:00:00", + "currency": "CZK", + "diffPrice": -2550.91, + "variableSymbol": 3003190328, + "supplier": { + "name": "VIVANTIS a.s.", + ... + "address": { + "street": "Školní náměstí 14", + "city": "Chrudim", + "zip": "537 01", + "country": "CZ" + } + }, + "customer": { + "name": "Internet Mall, a.s.", + ... + "address": { + "street": "U garáží 1611\/1", + "city": "Praha 7 - Holešovice", + "zip": "170 00", + "country": "CZ" + } + }, + "invoices": [ + { + "id": 3003000034, + ... + } + ], + "orders": [ + { + "id": 10016693501, + ... + } + ], + "attachment": { + "filename": "MP_offset_0000003000-28032019.PDF", + "mime": "application\/pdf" + } +} +``` + +## Download offset attachment + +Method returns [Psr\Http\Message\ResponseInterface](https://www.php-fig.org/psr/psr-7/). + +```php +use MpApiClient\Common\Interfaces\FinancialClientInterface; + +/** @var FinancialClientInterface $financialClient */ +$response = $financialClient->downloadOffset('offset-id'); + +header('Content-Type: ' . $response->getHeaderLine('Content-Type')); +echo $response->getBody()->getContents(); +``` + +Example above displays offset attachment in a browser. + +### See more examples [here](../example/Financial.php) diff --git a/doc/Label.md b/doc/Label.md new file mode 100644 index 0000000..6e49924 --- /dev/null +++ b/doc/Label.md @@ -0,0 +1,39 @@ +# Label client + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get list of all labels + +Method returns [LabelIterator](../src/Label/Entity/LabelIterator.php) containing [Label](../src/Label/Entity/Label.php) entity. + +```php +use MpApiClient\Common\Interfaces\LabelClientInterface; + +/** @var LabelClientInterface $labelClient */ +$labels = $labelClient->list(); +echo json_encode($labels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "id": "TIP2", + "title": "Náš tip" + }, + { + "id": "SALE", + "title": "Výprodej" + }, + { + "id": "FDEL", + "title": "Doprava zdarma" + }, + ... +] +``` + +### See more examples [here](../example/Label.php) diff --git a/doc/Order.md b/doc/Order.md new file mode 100644 index 0000000..94138d1 --- /dev/null +++ b/doc/Order.md @@ -0,0 +1,9 @@ +# OrderClient + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## TODO add documentation + +### See more examples [here](../example/Order.php) diff --git a/doc/Shop.md b/doc/Shop.md new file mode 100644 index 0000000..b11a752 --- /dev/null +++ b/doc/Shop.md @@ -0,0 +1,43 @@ +# Shop client + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get list of all shops + +Method returns [ShopIterator](../src/Shop/Entity/ShopIterator.php) containing [Shop](../src/Shop/Entity/Shop.php) entity. + +```php +use MpApiClient\Common\Interfaces\ShopClientInterface; + +/** @var ShopClientInterface $shopClient */ +$shops = $shopClient->list(); +echo json_encode($shops, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +[ + { + "shopId": "SK10MA", + "countryId": "SK", + "name": "Mall.sk", + "currencyIso": "EUR", + "currencySymbol": "€", + "url": "https:\/\/mpapi.mall.sk" + }, + { + "shopId": "CZ10MA", + "countryId": "CZ", + "name": "Mall.cz", + "currencyIso": "CZK", + "currencySymbol": "Kč", + "url": "https:\/\/mpapi.mall.cz" + }, + ... +] +``` + +### See more examples [here](../example/Shop.php) diff --git a/doc/SupplyDelay.md b/doc/SupplyDelay.md new file mode 100644 index 0000000..1b8fd7f --- /dev/null +++ b/doc/SupplyDelay.md @@ -0,0 +1,175 @@ +# SupplyDelay client + +## Client initialization + +To see example of initialization, please look at [Implementation](../README.md#implementation) part of our [README](../README.md) + +## Get partner supply delay + +Method returns [SupplyDelay](../src/SupplyDelay/Entity/SupplyDelay.php) entity. + +```php +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelay = $supplyDelayClient->get(); +echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "validFrom": "2020-12-22 00:00:00", + "validTo": "2021-01-06 08:00:00" +} +``` + +## Upsert partner supply delay + +- Upsert is short for Update-Insert +- Performs update of existing entity or creates new one if none exists (this eliminates the need of `create` and `update` methods) + +Method expects and returns [SupplyDelay](../src/SupplyDelay/Entity/SupplyDelay.php) entity. + +```php +use MpApiClient\SupplyDelay\Entity\SupplyDelay; + +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelay = $supplyDelayClient->upsert( + new SupplyDelay( + new DateTime('now'), + new DateTime('now + 1month'), + ) +); +var_dump($supplyDelay); +``` + +Example above prints out + +```json +{ + "validFrom": "2021-01-01 00:00:00", + "validTo": "2021-02-01 00:00:00" +} +``` + +## Delete partner supply delay + +```php +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelayClient->delete(); +``` + +## Get product supply delay + +Method returns [SupplyDelay](../src/SupplyDelay/Entity/SupplyDelay.php) entity. + +```php +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelay = $supplyDelayClient->getForProduct('product-id'); +echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "validFrom": "2020-12-22 00:00:00", + "validTo": "2021-01-06 08:00:00" +} +``` + +## Upsert product supply delay + +- Upsert is short for Update-Insert +- Performs update of existing entity or creates new one if none exists (this eliminates the need of `create` and `update` methods) + +Method expects and returns [SupplyDelay](../src/SupplyDelay/Entity/SupplyDelay.php) entity. + +```php +use MpApiClient\SupplyDelay\Entity\SupplyDelay; + +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelay = $supplyDelayClient->upsertForProduct( + 'product-id', + new SupplyDelay( + new DateTime('now'), + new DateTime('now + 1month'), + ) +); +var_dump($supplyDelay); +``` + +Example above prints out + +```json +{ + "validFrom": "2021-01-01 00:00:00", + "validTo": "2021-02-01 00:00:00" +} +``` + +## Delete product supply delay + +```php +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelayClient->deleteForProduct('product-id'); +``` + +## Get variant supply delay + +Method returns [SupplyDelay](../src/SupplyDelay/Entity/SupplyDelay.php) entity. + +```php +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelay = $supplyDelayClient->getForVariant('product-id', 'variant-id'); +echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +Example above prints out + +```json +{ + "validFrom": "2020-12-22 00:00:00", + "validTo": "2021-01-06 08:00:00" +} +``` + +## Upsert variant supply delay + +- Upsert is short for Update-Insert +- Performs update of existing entity or creates new one if none exists (this eliminates the need of `create` and `update` methods) + +Method expects and returns [SupplyDelay](../src/SupplyDelay/Entity/SupplyDelay.php) entity. + +```php +use MpApiClient\SupplyDelay\Entity\SupplyDelay; + +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelay = $supplyDelayClient->upsertForVariant( + 'product-id', + 'variant-id', + new SupplyDelay( + new DateTime('now'), + new DateTime('now + 1month'), + ) +); +var_dump($supplyDelay); +``` + +Example above prints out + +```json +{ + "validFrom": "2021-01-01 00:00:00", + "validTo": "2021-02-01 00:00:00" +} +``` + +## Delete variant supply delay + +```php +/** @var MpApiClient\Common\Interfaces\SupplyDelayClientInterface $supplyDelayClient */ +$supplyDelayClient->deleteForVariant('product-id', 'variant-id'); +``` + +### See more examples [here](../example/SupplyDelay.php) diff --git a/example/Brand.php b/example/Brand.php new file mode 100644 index 0000000..4e8c60c --- /dev/null +++ b/example/Brand.php @@ -0,0 +1,44 @@ +brand()->list(); + + // Print all brands as json object + echo json_encode($brandList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all brands as array + // [ + // [ + // 'brandId' => 'BRAND', + // 'title' => 'Brand title' + // ], + // ... + // ] + var_dump($brandList->jsonSerialize()); + + // Iterate over the returned list + foreach ($brandList as $brand) { + echo 'Brand id: ' . $brand->getBrandId() . PHP_EOL; + echo 'Title: ' . $brand->getTitle() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while loading brand list: ' . $e->getMessage(); +} + diff --git a/example/Category.php b/example/Category.php new file mode 100644 index 0000000..0ed06c0 --- /dev/null +++ b/example/Category.php @@ -0,0 +1,94 @@ +category()->list(); + + // Print all categories as json object + echo json_encode($categoryList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all categories as array + // [ + // [ + // 'categoryId': 'EA001', + // 'title': 'Kuchynské batérie' + // ], + // ... + // ] + var_dump($categoryList->jsonSerialize()); + + // Iterate over the returned list + foreach ($categoryList as $category) { + echo 'Category id: ' . $category->getCategoryId() . PHP_EOL; + echo 'Title: ' . $category->getTitle() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred while loading category list: ' . $e->getMessage(); +} + +// +// Get category parameters +// + +try { + $categoryParams = $client->category()->getParameters('EA001'); + + // Print all parameters as json object + echo json_encode($categoryParams, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all parameters as array + var_dump($categoryParams->jsonSerialize()); + + // Iterate over the returned list + foreach ($categoryParams as $param) { + echo 'Param id: ' . $param->getParamId() . PHP_EOL; + echo 'Title: ' . $param->getTitle() . PHP_EOL; + echo 'Unit: ' . $param->getUnit() . PHP_EOL; + echo 'Value count: ' . $param->getValues()->count() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred while loading category parameters: ' . $e->getMessage(); +} + +// +// Get category tree +// + +try { + $categoryTree = $client->category()->tree(ShopIdEnum::CZ10MA()); + + // Print entire tree as json object + echo json_encode($categoryTree, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get entire tree as array + var_dump($categoryTree->jsonSerialize()); + + // Iterating over entire tree requires recursion (not shown here) + foreach ($categoryTree as $treeItem) { + echo 'Title: ' . $treeItem->getTitle() . PHP_EOL; + echo 'Visible: ' . (int) $treeItem->isCategoryVisible() . PHP_EOL; + echo 'Items count: ' . $treeItem->getItems()->count() . PHP_EOL; + echo 'Menu items count: ' . $treeItem->getItems()->count() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred while loading category tree: ' . $e->getMessage(); +} diff --git a/example/Checks.php b/example/Checks.php new file mode 100644 index 0000000..0a6ccdf --- /dev/null +++ b/example/Checks.php @@ -0,0 +1,89 @@ +checks()->getMediaErrors(); + + // Print all media errors as json object + echo json_encode($mediaErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all media errors as array + // [ + // [ + // 'code' => 'MISSING_PACKAGE_DELIVERY', + // 'attribute' => 'package_size', + // 'value' => 'smallbox', + // 'msg' => 'There is no delivery for \'smallbox\' package size.', + // 'articles' => [ + // '1047' + // ] + // ], + // ... + // ] + var_dump($mediaErrors->jsonSerialize()); + + // Iterate over the returned list + foreach ($mediaErrors as $mediaError) { + echo 'Code: ' . $mediaError->getCode() . PHP_EOL; + echo 'Attribute: ' . $mediaError->getAttribute() . PHP_EOL; + echo 'Value: ' . $mediaError->getValue() . PHP_EOL; + echo 'Msg: ' . $mediaError->getMsg() . PHP_EOL; + echo 'Articles: ' . implode(', ', $mediaError->getArticles()) . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while loading media errors: ' . $e->getMessage(); +} + +// +// Get delivery errors +// + +try { + $deliveryErrors = $client->checks()->getDeliveryErrors(); + + // Print all delivery errors as json object + echo json_encode($deliveryErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all delivery errors as array + // [ + // [ + // 'code' => 'MEDIA_VALIDATION_ERROR', + // 'attribute' => 'media', + // 'value' => 'https:\/\/cdn.my-company.cz\/unsupported-image\/large.jpg', + // 'msg' => 'Unsupported mime type', + // 'articles' => [ + // '0234030' + // ] + // ], + // ... + // ] + var_dump($deliveryErrors->jsonSerialize()); + + // Iterate over the returned list + foreach ($deliveryErrors as $mediaError) { + echo 'Code: ' . $mediaError->getCode() . PHP_EOL; + echo 'Attribute: ' . $mediaError->getAttribute() . PHP_EOL; + echo 'Value: ' . $mediaError->getValue() . PHP_EOL; + echo 'Msg: ' . $mediaError->getMsg() . PHP_EOL; + echo 'Articles: ' . implode(', ', $mediaError->getArticles()) . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while loading delivery errors: ' . $e->getMessage(); +} diff --git a/example/Exception.php b/example/Exception.php new file mode 100644 index 0000000..7b8b2f7 --- /dev/null +++ b/example/Exception.php @@ -0,0 +1,50 @@ +orders()->get(1234567890); +} catch (UnauthorizedException $e) { + // You provided invalid client id, or forgot to provide authenticator middleware altogether + echo 'API authorization failed with error: ' . $e->getMessage(); +} catch (TooManyRequestsException $e) { + // Too many requests were sent to API and you got rate limited + + // Current max limit of requests for window + echo $e->getResponse()->getHeaderLine('X-RateLimit-Limit') . PHP_EOL; + // Remaining amount of requests in the time window + echo $e->getResponse()->getHeaderLine('X-RateLimit-Remaining') . PHP_EOL; + // Amount of seconds before the rate limit window resets + echo $e->getResponse()->getHeaderLine('X-RateLimit-Reset') . PHP_EOL; +} catch (NotFoundException $e) { + echo 'Order with id 1234567890 does not exist.'; +} catch (BadResponseException $e) { + echo 'Request failed with error: ' . $e->getMessage(); + + foreach ($e->getErrorCodes() as $errorCode) { + echo 'Error message: ' . $errorCode->getMessage() . PHP_EOL; + echo 'Error code: ' . $errorCode->getCode() . PHP_EOL; + echo 'Error attributes: ' . print_r($errorCode->getAttributes(), true) . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while loading order list: ' . $e->getMessage(); +} catch (Exception $e) { + echo 'Unexpected generic error occurred, while loading order list: ' . $e->getMessage(); +} diff --git a/example/Financial.php b/example/Financial.php new file mode 100644 index 0000000..17bdd48 --- /dev/null +++ b/example/Financial.php @@ -0,0 +1,143 @@ +financial()->listInvoices(null); + + // Print all invoices as json object + echo json_encode($invoiceList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all invoices as array + var_dump($invoiceList->jsonSerialize()); + + // Iterate over the returned list + foreach ($invoiceList as $invoice) { + echo 'Invoice number: ' . $invoice->getInvoiceNumber() . PHP_EOL; + echo 'Created at: ' . $invoice->getCreatedAt()->format(DATE_RFC3339) . PHP_EOL; + echo 'Total: ' . $invoice->getTotal() . PHP_EOL; + echo 'Currency: ' . $invoice->getCurrency() . PHP_EOL; + echo 'Item count: ' . $invoice->getItems()->count() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading invoice list: ' . $e->getMessage(); +} + +// +// Get invoice detail +// + +try { + $invoice = $client->financial()->getInvoice('invoice-id'); + + // Print invoice as json object + echo json_encode($invoice, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get invoice as array + var_dump($invoice->jsonSerialize()); + + // Show some invoice data + echo 'Created at: ' . $invoice->getCreatedAt()->format(DATE_RFC3339) . PHP_EOL; + echo 'Total: ' . $invoice->getTotal() . PHP_EOL; + echo 'Currency: ' . $invoice->getCurrency() . PHP_EOL; + echo 'Item count: ' . $invoice->getItems()->count() . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading invoice: ' . $e->getMessage(); +} + +// +// Download invoice +// + +try { + $response = $client->financial()->downloadInvoice('invoice-id'); + + header('Content-Type: ' . $response->getHeaderLine('Content-Type')); + // this header forces browser to download the file, comment out to display attachment in a browser + header('Content-Disposition: attachment; filename="invoice-id.pdf"'); + echo $response->getBody()->getContents(); +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while downloading invoice: ' . $e->getMessage(); +} + +// +// Get offset list +// + +try { + $offsetsList = $client->financial()->listOffsets(null); + + // Print all offsets as json object + echo json_encode($offsetsList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all invoices as array + var_dump($offsetsList->jsonSerialize()); + + // Iterate over the returned list + foreach ($offsetsList as $offset) { + echo 'Document number: ' . $offset->getDocumentNumber() . PHP_EOL; + echo 'Created at: ' . $offset->getCreatedAt()->format(DATE_RFC3339) . PHP_EOL; + echo 'Diff price: ' . $offset->getDiffPrice() . PHP_EOL; + echo 'Currency: ' . $offset->getCurrency() . PHP_EOL; + echo 'Invoice count: ' . $offset->getInvoices()->count() . PHP_EOL; + echo 'Order count: ' . $offset->getOrders()->count() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading offset list: ' . $e->getMessage(); +} + +// +// Get offset detail +// + +try { + $offset = $client->financial()->getOffset('offset-id'); + + // Print offset as json object + echo json_encode($offset, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get offset as array + var_dump($offset->jsonSerialize()); + + // Show some offset data + echo 'Document number: ' . $offset->getDocumentNumber() . PHP_EOL; + echo 'Created at: ' . $offset->getCreatedAt()->format(DATE_RFC3339) . PHP_EOL; + echo 'Diff price: ' . $offset->getDiffPrice() . PHP_EOL; + echo 'Currency: ' . $offset->getCurrency() . PHP_EOL; + echo 'Attachment: ' . $offset->getAttachment()->getFilename() . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading offset: ' . $e->getMessage(); +} + +// +// Download offset +// + +try { + $response = $client->financial()->downloadOffset('offset-id'); + + header('Content-Type: ' . $response->getHeaderLine('Content-Type')); + // this header forces browser to download the file, comment out to display attachment in a browser + header('Content-Disposition: attachment; filename="offset-id.pdf"'); + echo $response->getBody()->getContents(); +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while downloading offset: ' . $e->getMessage(); +} diff --git a/example/Label.php b/example/Label.php new file mode 100644 index 0000000..8a47671 --- /dev/null +++ b/example/Label.php @@ -0,0 +1,44 @@ +label()->list(); + + // Print all labels as json object + echo json_encode($labelList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all labels as array + // [ + // [ + // 'id' => 'SALE', + // 'title' => 'Výprodej' + // ], + // ... + // ] + var_dump($labelList->jsonSerialize()); + + // Iterate over the returned list + foreach ($labelList as $label) { + echo 'Label id: ' . $label->getId() . PHP_EOL; + echo 'Title: ' . $label->getTitle() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while loading label list: ' . $e->getMessage(); +} + diff --git a/example/Order.php b/example/Order.php new file mode 100644 index 0000000..414f7dd --- /dev/null +++ b/example/Order.php @@ -0,0 +1,66 @@ +orders()->list(null); +echo json_encode($orderList, JSON_PRETTY_PRINT); + +// 4. Send query to order list endpoint with filter where +// - `status` field is equal to `open` +// - `id` field is between 100 and 10000 (inclusive) +// - results are sorted by `id` field in descending direction +$filter = new Filter(); +$filter->addFilterItem(FilterItem::create("status", StatusEnum::OPEN()->getValue(), FilterOperatorEnum::EQUAL())); +$filter->addFilterItem(FilterItem::createInterval("cod", "100", "10000")); +$filter->addSortColumn('id', Filter::DIRECTION_DESC); + +$filteredOrderList = $client->orders()->list($filter); +// $filteredOrderList->enableAutoload(true); +echo json_encode($filteredOrderList, JSON_PRETTY_PRINT); + +// 5. Get single order by ID +$orderDetail = $client->orders()->get($orderId); +echo json_encode($orderDetail, JSON_PRETTY_PRINT); + +// 6. Get order statistics for past 14 days +$orderStats = $client->orders()->stats(14); +echo json_encode($orderStats, JSON_PRETTY_PRINT); + +// 7. Confirm/acknowledge new changes on an order (unconfirmed orders can not have their status changed) +$client->orders()->confirmOrder($orderId); + +// 8. Update order status +$statusRequest = new StatusRequest(StatusEnum::shipping()); +$statusRequest->setTracking('ABC123456', 'https://tracking.company.com/id/ABC123456'); +$client->orders()->setStatus($orderId, $statusRequest); + +// 9. Set tracking for shipped order (should be set in one request during status update) +$client->orders()->setItemSerialNumbers($orderId, 1234567, 'SN12345', 'SN23456'); + +// 10. Get order shipping labels to print on the box for two orders +$labelRequest = new ShippingLabelRequest(ShippingLabelRequest::TYPE_PDF, 1, 4); +$labelRequest->addLabel($orderId, 2); +$labelRequest->addLabel($orderId2, 1); +$labels = $client->orders()->createShippingLabels($labelRequest); +echo $labels; diff --git a/example/Products.php b/example/Products.php new file mode 100644 index 0000000..b6c5e58 --- /dev/null +++ b/example/Products.php @@ -0,0 +1,55 @@ +article()->listProducts(null); +echo json_encode($productList, JSON_PRETTY_PRINT); + +// 4. Send query to product list endpoint with filter where +// - `category_id` field is equal to EB036 +// - results are sorted always by `id` field in ascending direction and this can not be changed by filters +$filter = new Filter(); +$filter->addFilterItem(FilterItem::create("_category_id", "EG115", FilterOperatorEnum::EQUAL())); +// Load all results and enable autoload, to iterate over all returned pages +$filteredProductList = $client->article()->listProducts($filter); +$filteredProductList->enableAutoload(); +echo json_encode($filteredProductList, JSON_PRETTY_PRINT); + +// 5. Get single product by ID +$productDetail = $client->article()->getProduct($productId); +echo json_encode($productDetail, JSON_PRETTY_PRINT); + +// 6. Get product availability +$availability = $client->article()->getProductAvailability($productId); +echo json_encode($availability, JSON_PRETTY_PRINT); + +// 7. List all variants for product +$variantList = $client->article()->listProductVariants($productId, null); +echo json_encode($variantList, JSON_PRETTY_PRINT); + +// 8. Get variant detail +$variantDetail = $client->article()->getVariant($productId, $variantId); +echo json_encode($variantDetail, JSON_PRETTY_PRINT); + +// 9. Get variant availability +$variantAvailability = $client->article()->getVariantAvailability($productId, $variantId); +echo json_encode($variantAvailability, JSON_PRETTY_PRINT); diff --git a/example/Shop.php b/example/Shop.php new file mode 100644 index 0000000..3d80de3 --- /dev/null +++ b/example/Shop.php @@ -0,0 +1,55 @@ +shop()->list(); + + // Print all shops as json object + echo json_encode($shopList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get all shops as array + // [ + // [ + // 'shopId' => 'SK10MA', + // 'countryId' => 'SK', + // 'name' => 'Mall.sk', + // 'currencyIso' => 'EUR', + // 'currencySymbol' => '€', + // 'url' => 'https:\/\/mpapi.mall.sk' + // ], + // ... + // ] + var_dump($shopList->jsonSerialize()); + + // Get one shop from the list using `getByShopId` method + var_dump($shopList->getByShopId(ShopIdEnum::CZ10MA())); + + // Iterate over the returned list + foreach ($shopList as $shop) { + echo 'ShopId: ' . $shop->getShopId()->getValue() . PHP_EOL; + echo 'CountryId: ' . $shop->getCountryId() . PHP_EOL; + echo 'Name: ' . $shop->getName() . PHP_EOL; + echo 'CurrencyIso: ' . $shop->getCurrencyIso() . PHP_EOL; + echo 'CurrencySymbol: ' . $shop->getCurrencySymbol() . PHP_EOL; + echo 'Url: ' . $shop->getUrl() . PHP_EOL; + echo PHP_EOL; + } +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while loading shop list: ' . $e->getMessage(); +} diff --git a/example/SupplyDelay.php b/example/SupplyDelay.php new file mode 100644 index 0000000..b4abeb8 --- /dev/null +++ b/example/SupplyDelay.php @@ -0,0 +1,218 @@ +supplyDelay()->get(); + + // Print supply delay as json object + echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get supply delay as array + // [ + // 'validFrom' => '2021-01-01 00:00:00', + // 'validTo' => '2021-02-01 00:00:00', + // ] + var_dump($supplyDelay->jsonSerialize()); + + // Show formatted supply delay validity dates + echo 'Valid from: ' . $supplyDelay->getValidFrom()->format(DATE_RFC3339) . PHP_EOL; + echo 'Valid to: ' . $supplyDelay->getValidTo()->format(DATE_RFC3339) . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading supply delay: ' . $e->getMessage(); +} + +// +// Create or update global supply delay +// + +try { + // Created supply delay is returned back + $supplyDelay = $client->supplyDelay()->upsert( + new SupplyDelay( + new DateTime('now'), + new DateTime('now + 1month'), + ) + ); + + // Print supply delay as json object + echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get supply delay as array + // [ + // 'validFrom' => '2021-01-01 00:00:00', + // 'validTo' => '2021-02-01 00:00:00', + // ] + var_dump($supplyDelay->jsonSerialize()); + + // Show formatted supply delay validity dates + echo 'Valid from: ' . $supplyDelay->getValidFrom()->format(DATE_RFC3339) . PHP_EOL; + echo 'Valid to: ' . $supplyDelay->getValidTo()->format(DATE_RFC3339) . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while creating/updating supply delay: ' . $e->getMessage(); +} + +// +// Delete global supply delay +// + +try { + // Method should return nothing if delete was successful + $client->supplyDelay()->delete(); +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while deleting supply delay: ' . $e->getMessage(); +} + +// +// Get product supply delay +// + +try { + $supplyDelay = $client->supplyDelay()->getForProduct('product-id'); + + // Print supply delay as json object + echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get supply delay as array + // [ + // 'validFrom' => '2021-01-01 00:00:00', + // 'validTo' => '2021-02-01 00:00:00', + // ] + var_dump($supplyDelay->jsonSerialize()); + + // Show formatted supply delay validity dates + echo 'Valid from: ' . $supplyDelay->getValidFrom()->format(DATE_RFC3339) . PHP_EOL; + echo 'Valid to: ' . $supplyDelay->getValidTo()->format(DATE_RFC3339) . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading product supply delay: ' . $e->getMessage(); +} + +// +// Create or update product supply delay +// + +try { + // Created supply delay is returned back + $supplyDelay = $client->supplyDelay()->upsertForProduct( + 'product-id', + new SupplyDelay( + new DateTime('now'), + new DateTime('now + 1month'), + ) + ); + + // Print supply delay as json object + echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get supply delay as array + // [ + // 'validFrom' => '2021-01-01 00:00:00', + // 'validTo' => '2021-02-01 00:00:00', + // ] + var_dump($supplyDelay->jsonSerialize()); + + // Show formatted supply delay validity dates + echo 'Valid from: ' . $supplyDelay->getValidFrom()->format(DATE_RFC3339) . PHP_EOL; + echo 'Valid to: ' . $supplyDelay->getValidTo()->format(DATE_RFC3339) . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while creating/updating product supply delay: ' . $e->getMessage(); +} + +// +// Delete product supply delay +// + +try { + // Method should return nothing if delete was successful + $client->supplyDelay()->deleteForProduct('product-id'); +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while deleting product supply delay: ' . $e->getMessage(); +} + +// +// Get variant supply delay +// + +try { + $supplyDelay = $client->supplyDelay()->getForVariant('product-id', 'variant-id'); + + // Print supply delay as json object + echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get supply delay as array + // [ + // 'validFrom' => '2021-01-01 00:00:00', + // 'validTo' => '2021-02-01 00:00:00', + // ] + var_dump($supplyDelay->jsonSerialize()); + + // Show formatted supply delay validity dates + echo 'Valid from: ' . $supplyDelay->getValidFrom()->format(DATE_RFC3339) . PHP_EOL; + echo 'Valid to: ' . $supplyDelay->getValidTo()->format(DATE_RFC3339) . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while loading variant supply delay: ' . $e->getMessage(); +} + +// +// Create or update variant supply delay +// + +try { + // Created supply delay is returned back + $supplyDelay = $client->supplyDelay()->upsertForVariant( + 'product-id', + 'variant-id', + new SupplyDelay( + new DateTime('now'), + new DateTime('now + 1month'), + ) + ); + + // Print supply delay as json object + echo json_encode($supplyDelay, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + // Get supply delay as array + // [ + // 'validFrom' => '2021-01-01 00:00:00', + // 'validTo' => '2021-02-01 00:00:00', + // ] + var_dump($supplyDelay->jsonSerialize()); + + // Show formatted supply delay validity dates + echo 'Valid from: ' . $supplyDelay->getValidFrom()->format(DATE_RFC3339) . PHP_EOL; + echo 'Valid to: ' . $supplyDelay->getValidTo()->format(DATE_RFC3339) . PHP_EOL; + echo PHP_EOL; +} catch (MpApiException | Exception $e) { + echo 'Unexpected error occurred, while creating/updating variant supply delay: ' . $e->getMessage(); +} + +// +// Delete product supply delay +// + +try { + // Method should return nothing if delete was successful + $client->supplyDelay()->deleteForVariant('product-id', 'variant-id'); +} catch (MpApiException $e) { + echo 'Unexpected error occurred, while deleting variant supply delay: ' . $e->getMessage(); +} diff --git a/phpcs-ruleset.xml b/phpcs-ruleset.xml new file mode 100644 index 0000000..cb71fbe --- /dev/null +++ b/phpcs-ruleset.xml @@ -0,0 +1,106 @@ + + + Marketplace standards + example/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..8ed0fb2 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,66 @@ + + + + + + Allow "else" expression. + + Allow factory methods. + + Allow DTOs and Entities that contain bool properties (there is no way to add exceptions). + + + + + Exclude variable and method length restriction to allow custom length definition. + + + + + Allow variables with name length of 2 characters (e.g. $id, $to). + + + + + + + Allow variables with name length up to 30 characters. + + + + + + + Allow entities with more than 10 parameters. + + Allow entities with more than 15 fields. + + Allow clients with more public methods. + + + + + + + + + + + + + + + + Allow MpApiClient itself, which couples all domain clients together. + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..5a7da5d --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,31 @@ +parameters: + inferPrivatePropertyTypeFromConstructor: false + checkGenericClassInNonGenericObjectType: false + tmpDir: tmp/phpstan + level: max + bootstrapFiles: + - src/MpApiClient.php + paths: + - src + - tests + + # Exclude auto generated codeception files from static analysis + excludes_analyse: + - */tests/_data/* + - */tests/_support/_generated/* + # Ignore functional tests, because PHPStan does not support Codeception Asserts module in Functional test suite + - */tests/functional/* + + ignoreErrors: + - + # we do not know what is the exact content of the response (it can be single object, or list of objects etc.) + message: '#Method MpApiClient\\Common\\AbstractMpApiClient::sendQueryRequest\(\) return type has no value type specified in iterable type array#' + path: src/Common/AbstractMpApiClient.php + - + # we do not know what is the exact content of the request (it can be single object, or list of objects etc.) + message: '#Method MpApiClient\\Common\\AbstractMpApiClient::sendJson\(\) has parameter \$content with no value type specified in iterable type array#' + path: src/Common/AbstractMpApiClient.php + - + # we do not know what is the exact content of the response (it can be single object, or list of objects etc.) + message: '#Method MpApiClient\\Common\\AbstractMpApiClient::sendJson\(\) return type has no value type specified in iterable type array#' + path: src/Common/AbstractMpApiClient.php diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..4e9226b --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/Article/ArticleClient.php b/src/Article/ArticleClient.php new file mode 100644 index 0000000..fa9a985 --- /dev/null +++ b/src/Article/ArticleClient.php @@ -0,0 +1,167 @@ +addFilterItem(FilterItem::create('filter', 'basic', FilterOperatorEnum::EMPTY())); + + return BasicProductList::createWithCallback( + Closure::fromCallable( + fn(Filter $filter): array => $this->sendQueryRequest(self::PRODUCT_LIST, $filter->buildFilterQuery()) + ), + $filter, + ); + } + + public function getProduct(string $productId): Product + { + return Product::createFromApi( + $this->sendJson('GET', sprintf(self::PRODUCT_DETAIL, $productId))['data'] + ); + } + + public function createProduct(ProductRequest $product): Product + { + return Product::createFromApi( + $this->sendJson('POST', self::PRODUCT_LIST, $product->getArrayForApi())['data'] + ); + } + + public function updateProduct(ProductRequest $product): void + { + $this->sendJson('PUT', sprintf(self::PRODUCT_DETAIL, $product->getId()), $product->getArrayForApi()); + } + + public function deleteProduct(string $productId): void + { + $this->sendJson('DELETE', sprintf(self::PRODUCT_DETAIL, $productId)); + } + + public function getProductAvailability(string $productId): Availability + { + return Availability::createFromApi( + $this->sendJson('GET', sprintf(self::PRODUCT_AVAILABILITY, $productId))['data'] + ); + } + + public function getProductPricing(string $productId): Pricing + { + return Pricing::createFromApi( + $this->sendJson('GET', sprintf(self::PRODUCT_PRICING, $productId))['data'] + ); + } + + public function updateProductPricing(string $productId, Pricing $pricing): void + { + $this->sendJson('PUT', sprintf(self::PRODUCT_PRICING, $productId), $pricing->getArrayForApi()); + } + + public function listProductVariants(string $productId, ?Filter $filter): BasicVariantList + { + $filter ??= new Filter(); + // client supports only list of basic variants + $filter->addFilterItem(FilterItem::create('filter', 'basic', FilterOperatorEnum::EMPTY())); + + return BasicVariantList::createWithCallback( + Closure::fromCallable( + fn(Filter $filter): array => $this->sendQueryRequest(sprintf(self::VARIANT_LIST, $productId), $filter->buildFilterQuery()) + ), + $filter, + ); + } + + public function getVariant(string $productId, string $variantId): Variant + { + return Variant::createFromApi( + $this->sendJson('GET', sprintf(self::VARIANT_DETAIL, $productId, $variantId))['data'] + ); + } + + public function createVariant(string $productId, VariantRequest $variant): Variant + { + return Variant::createFromApi( + $this->sendJson('POST', sprintf(self::VARIANT_LIST, $productId), $variant->getArrayForApi())['data'] + ); + } + + public function updateVariant(string $productId, VariantRequest $variant): void + { + $this->sendJson('PUT', sprintf(self::VARIANT_DETAIL, $productId, $variant->getId()), $variant->getArrayForApi()); + } + + public function deleteVariant(string $productId, string $variantId): void + { + $this->sendJson('DELETE', sprintf(self::VARIANT_DETAIL, $productId, $variantId)); + } + + public function getVariantAvailability(string $productId, string $variantId): Availability + { + return Availability::createFromApi( + $this->sendJson('GET', sprintf(self::VARIANT_AVAILABILITY, $productId, $variantId))['data'] + ); + } + + public function getVariantPricing(string $productId, string $variantId): Pricing + { + return Pricing::createFromApi( + $this->sendJson('GET', sprintf(self::VARIANT_PRICING, $productId, $variantId))['data'] + ); + } + + public function updateVariantPricing(string $productId, string $variantId, Pricing $pricing): void + { + $this->sendJson('PUT', sprintf(self::VARIANT_PRICING, $productId, $variantId), $pricing->getArrayForApi()); + } + + public function updateBatchAvailability(BatchAvailabilityIterator $availability): void + { + $this->sendJson('POST', self::BATCH_AVAILABILITY, $availability->getArrayForApi()); + } + + public function activateAllProducts(): void + { + $this->sendJson('PUT', self::BATCH_ACTIVATE_ALL); + } + + public function activateSelectedProducts(string ...$productIds): void + { + $this->sendJson('PUT', self::BATCH_ACTIVATE_SELECTED, array_map(fn(string $productId): array => ['productId' => $productId], $productIds)); + } + +} diff --git a/src/Article/DTO/AbstractArticleRequest.php b/src/Article/DTO/AbstractArticleRequest.php new file mode 100644 index 0000000..0a0543a --- /dev/null +++ b/src/Article/DTO/AbstractArticleRequest.php @@ -0,0 +1,261 @@ +id = $id; + $this->title = $title; + $this->shortDesc = $shortDesc; + $this->longDesc = $longDesc; + $this->priority = $priority; + + // All iterators can be empty, even if they are not strictly mandatory (removal of nullability eases the workflow a lot) + $this->media = new MediaIterator(); + $this->promotions = new PromotionIterator(); + $this->parameters = new ParameterIterator(); + $this->labels = new LabelIterator(); + } + + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): void + { + $this->title = $title; + } + + public function getShortDesc(): string + { + return $this->shortDesc; + } + + public function setShortDesc(string $shortDesc): void + { + $this->shortDesc = $shortDesc; + } + + public function getLongDesc(): string + { + return $this->longDesc; + } + + public function setLongDesc(string $longDesc): void + { + $this->longDesc = $longDesc; + } + + public function getPriority(): int + { + return $this->priority; + } + + public function setPriority(int $priority): void + { + $this->priority = $priority; + } + + public function getBarcode(): ?string + { + return $this->barcode; + } + + public function setBarcode(?string $barcode): void + { + $this->barcode = $barcode; + } + + public function getPurchasePrice(): ?float + { + return $this->purchasePrice; + } + + public function setPurchasePrice(?float $purchasePrice): void + { + $this->purchasePrice = $purchasePrice; + } + + public function getRrp(): ?float + { + return $this->rrp; + } + + public function setRrp(?float $rrp): void + { + $this->rrp = $rrp; + } + + public function getMedia(): MediaIterator + { + return $this->media; + } + + public function setMedia(MediaIterator $media): void + { + $this->media = $media; + } + + public function getPromotions(): PromotionIterator + { + return $this->promotions; + } + + public function setPromotions(PromotionIterator $promotions): void + { + $this->promotions = $promotions; + } + + public function getParameters(): ParameterIterator + { + return $this->parameters; + } + + public function setParameters(ParameterIterator $parameters): void + { + $this->parameters = $parameters; + } + + public function getLabels(): LabelIterator + { + return $this->labels; + } + + public function setLabels(LabelIterator $labels): void + { + $this->labels = $labels; + } + + /** + * @psalm-mutation-free + */ + public function getDimensions(): ?Dimensions + { + return $this->dimensions; + } + + public function setDimensions(?Dimensions $dimensions): void + { + $this->dimensions = $dimensions; + } + + /** + * @psalm-mutation-free + */ + public function getAvailability(): ?Availability + { + return $this->availability; + } + + public function setAvailability(?Availability $availability): void + { + $this->availability = $availability; + } + + /** + * @return string[] + */ + public function getRecommended(): array + { + return $this->recommended; + } + + public function setRecommended(string ...$recommended): void + { + $this->recommended = $recommended; + } + + public function getDeliveryDelay(): ?int + { + return $this->deliveryDelay; + } + + public function setDeliveryDelay(?int $deliveryDelay): void + { + $this->deliveryDelay = $deliveryDelay; + } + + public function getFreeDelivery(): ?bool + { + return $this->freeDelivery; + } + + public function setFreeDelivery(?bool $freeDelivery): void + { + $this->freeDelivery = $freeDelivery; + } + + /** + * @psalm-mutation-free + */ + public function getPackageSize(): ?PackageSizeEnum + { + return $this->packageSize; + } + + public function setPackageSize(?PackageSizeEnum $packageSize): void + { + $this->packageSize = $packageSize; + } + + public function getMallboxAllowed(): ?bool + { + return $this->mallboxAllowed; + } + + public function setMallboxAllowed(?bool $mallboxAllowed): void + { + $this->mallboxAllowed = $mallboxAllowed; + } + +} diff --git a/src/Article/DTO/BatchAvailability.php b/src/Article/DTO/BatchAvailability.php new file mode 100644 index 0000000..87991db --- /dev/null +++ b/src/Article/DTO/BatchAvailability.php @@ -0,0 +1,36 @@ +id = $id; + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'id' => $this->getId(), + StatusEnum::KEY_NAME => $this->getStatus()->getValue(), + 'in_stock' => $this->getInStock(), + ]; + } + + public function getId(): string + { + return $this->id; + } + +} diff --git a/src/Article/DTO/BatchAvailabilityIterator.php b/src/Article/DTO/BatchAvailabilityIterator.php new file mode 100644 index 0000000..0f38f15 --- /dev/null +++ b/src/Article/DTO/BatchAvailabilityIterator.php @@ -0,0 +1,52 @@ + + * @property array $data + */ +final class BatchAvailabilityIterator extends AbstractStringKeyIterator +{ + + public function __construct(BatchAvailability ...$data) + { + foreach ($data as $availability) { + $this->data[$availability->getId()] = $availability; + } + } + + /** + * @return array> + */ + public function getArrayForApi(): array + { + return array_map(fn(BatchAvailability $availability): array => $availability->getArrayForApi(), array_values($this->data)); + } + + /** + * @return false|BatchAvailability + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?BatchAvailability + { + return $this->data[$key] ?? null; + } + + public function remove(string $key): void + { + parent::remove($key); + } + + public function add(BatchAvailability $value): void + { + $this->data[$value->getId()] = $value; + } + +} diff --git a/src/Article/DTO/ProductRequest.php b/src/Article/DTO/ProductRequest.php new file mode 100644 index 0000000..2af7845 --- /dev/null +++ b/src/Article/DTO/ProductRequest.php @@ -0,0 +1,190 @@ +categoryId = $categoryId; + $this->vat = $vat; + + // All iterators can be empty, even if they are not strictly mandatory (removal of nullability eases the workflow a lot) + $this->variants = new VariantRequestIterator(); + } + + public static function createFromProduct(Product $product): self + { + $self = new self( + $product->getId(), + $product->getTitle(), + $product->getShortDesc(), + $product->getLongDesc(), + $product->getCategoryId(), + $product->getVat(), + $product->getPriority() + ); + + $self->setBarcode($product->getBarcode()); + $self->setPrice($product->getPrice()); + $self->setPurchasePrice($product->getPurchasePrice()); + $self->setRrp($product->getRrp()); + $self->setMedia($product->getMedia()); + $self->setPromotions($product->getPromotions()); + $self->setParameters($product->getParameters()); + $self->setDimensions($product->getDimensions()); + $self->setAvailability($product->getAvailability()); + $self->setLabels($product->getLabels()); + $self->setRecommended(...$product->getRecommended()); + $self->setDeliveryDelay($product->getDeliveryDelay()); + $self->setFreeDelivery($product->hasFreeDelivery()); + $self->setPackageSize($product->getPackageSize()); + $self->setMallboxAllowed($product->hasMallboxAllowed()); + $self->setVariableParameters(...$product->getVariableParameters()); + $self->setPartnerTitle($product->getPartnerTitle()); + $self->setBrandId($product->getBrandId()); + + /** @var Variant $variant - PHPStan false positive fix */ + foreach ($product->getVariants() as $variant) { + $self->getVariants()->add(VariantRequest::createFromVariant($variant)); + } + + return $self; + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + $mandatory = [ + 'id' => $this->getId(), + 'title' => $this->getTitle(), + 'shortdesc' => $this->getShortdesc(), + 'longdesc' => $this->getLongdesc(), + 'category_id' => $this->getCategoryId(), + 'vat' => $this->getVat(), + 'media' => $this->getMedia()->getArrayForApi(), + 'promotions' => $this->getPromotions()->getArrayForApi(), + 'variants' => $this->getVariants()->getArrayForApi(), + 'parameters' => $this->getParameters()->getArrayForApi(), + 'variable_parameters' => $this->getVariableParameters(), + ]; + + $optional = array_filter( + [ + 'priority' => $this->getPriority(), + 'barcode' => $this->getBarcode(), + 'price' => $this->getPrice(), + 'purchase_price' => $this->getPurchasePrice(), + 'rrp' => $this->getRrp(), + 'dimensions' => $this->getDimensions() === null ? null : $this->getDimensions()->getArrayForApi(), + 'availability' => $this->getAvailability() === null ? null : $this->getAvailability()->getArrayForApi(), + 'labels' => $this->getLabels()->getArrayForApi(), + 'recommended' => $this->getRecommended(), + 'delivery_delay' => $this->getDeliveryDelay(), + 'free_delivery' => $this->getFreeDelivery(), + 'package_size' => $this->getPackageSize() === null ? null : $this->getPackageSize()->getValue(), + 'mallbox_allowed' => $this->getMallboxAllowed(), + 'partner_title' => $this->getPartnerTitle(), + 'brand_id' => $this->getBrandId(), + ], + fn($value): bool => !is_null($value) + ); + + return array_merge($mandatory, $optional); + } + + public function getCategoryId(): string + { + return $this->categoryId; + } + + public function setCategoryId(string $categoryId): void + { + $this->categoryId = $categoryId; + } + + public function getVat(): int + { + return $this->vat; + } + + public function setVat(int $vat): void + { + $this->vat = $vat; + } + + public function getPrice(): ?float + { + return $this->price; + } + + public function setPrice(?float $price): void + { + $this->price = $price; + } + + public function getVariants(): VariantRequestIterator + { + return $this->variants; + } + + public function setVariants(VariantRequestIterator $variants): void + { + $this->variants = $variants; + } + + /** + * @return string[] + */ + public function getVariableParameters(): array + { + return $this->variableParameters; + } + + public function setVariableParameters(string ...$variableParameters): void + { + $this->variableParameters = $variableParameters; + } + + public function getPartnerTitle(): ?string + { + return $this->partnerTitle; + } + + public function setPartnerTitle(?string $partnerTitle): void + { + $this->partnerTitle = $partnerTitle; + } + + public function getBrandId(): ?string + { + return $this->brandId; + } + + public function setBrandId(?string $brandId): void + { + $this->brandId = $brandId; + } + +} diff --git a/src/Article/DTO/VariantRequest.php b/src/Article/DTO/VariantRequest.php new file mode 100644 index 0000000..69dbe5b --- /dev/null +++ b/src/Article/DTO/VariantRequest.php @@ -0,0 +1,106 @@ +price = $price; + $this->media = $media; + $this->parameters = $parameters; + } + + public static function createFromVariant(Variant $variant): self + { + $self = new self( + $variant->getId(), + $variant->getTitle(), + $variant->getShortDesc(), + $variant->getLongDesc(), + $variant->getPriority(), + $variant->getPrice(), + $variant->getMedia(), + $variant->getParameters(), + ); + + $self->setBarcode($variant->getBarcode()); + $self->setPurchasePrice($variant->getPurchasePrice()); + $self->setRrp($variant->getRrp()); + $self->setPromotions($variant->getPromotions()); + $self->setDimensions($variant->getDimensions()); + $self->setAvailability($variant->getAvailability()); + $self->setLabels($variant->getLabels()); + $self->setRecommended(...$variant->getRecommended()); + $self->setDeliveryDelay($variant->getDeliveryDelay()); + $self->setFreeDelivery($variant->hasFreeDelivery()); + $self->setPackageSize($variant->getPackageSize()); + $self->setMallboxAllowed($variant->hasMallboxAllowed()); + + return $self; + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + $mandatory = [ + 'id' => $this->getId(), + 'title' => $this->getTitle(), + 'shortdesc' => $this->getShortdesc(), + 'longdesc' => $this->getLongdesc(), + 'media' => $this->getMedia()->getArrayForApi(), + 'parameters' => $this->getParameters()->getArrayForApi(), + 'promotions' => $this->getPromotions()->getArrayForApi(), + 'priority' => $this->getPriority(), + 'price' => $this->getPrice(), + ]; + + $optional = array_filter( + [ + 'barcode' => $this->getBarcode(), + 'purchase_price' => $this->getPurchasePrice(), + 'rrp' => $this->getRrp(), + 'dimensions' => $this->getDimensions() === null ? null : $this->getDimensions()->getArrayForApi(), + 'availability' => $this->getAvailability() === null ? null : $this->getAvailability()->getArrayForApi(), + 'labels' => $this->getLabels()->getArrayForApi(), + 'recommended' => $this->getRecommended(), + 'delivery_delay' => $this->getDeliveryDelay(), + 'free_delivery' => $this->getFreeDelivery(), + 'package_size' => $this->getPackageSize() === null ? null : $this->getPackageSize()->getValue(), + 'mallbox_allowed' => $this->getMallboxAllowed(), + ], + fn($value): bool => !is_null($value) + ); + + return array_merge($mandatory, $optional); + } + + public function getPrice(): float + { + return $this->price; + } + + public function setPrice(float $price): void + { + $this->price = $price; + } + +} diff --git a/src/Article/DTO/VariantRequestIterator.php b/src/Article/DTO/VariantRequestIterator.php new file mode 100644 index 0000000..152f6fe --- /dev/null +++ b/src/Article/DTO/VariantRequestIterator.php @@ -0,0 +1,52 @@ + + * @property array $data + */ +final class VariantRequestIterator extends AbstractStringKeyIterator +{ + + public function __construct(VariantRequest ...$data) + { + foreach ($data as $variant) { + $this->data[$variant->getId()] = $variant; + } + } + + /** + * @return array> + */ + public function getArrayForApi(): array + { + return array_map(fn(VariantRequest $variantRequest): array => $variantRequest->getArrayForApi(), array_values($this->data)); + } + + /** + * @return false|VariantRequest + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?VariantRequest + { + return $this->data[$key] ?? null; + } + + public function remove(string $key): void + { + parent::remove($key); + } + + public function add(VariantRequest $value): void + { + $this->data[$value->getId()] = $value; + } + +} diff --git a/src/Article/Entity/BasicProduct.php b/src/Article/Entity/BasicProduct.php new file mode 100644 index 0000000..d2cbf9b --- /dev/null +++ b/src/Article/Entity/BasicProduct.php @@ -0,0 +1,140 @@ +id = $id; + $this->productId = $productId; + $this->title = $title; + $this->status = $status; + $this->stage = $stage; + $this->inStock = $inStock; + $this->categoryId = $categoryId; + $this->price = $price; + $this->purchasePrice = $purchasePrice; + $this->rrp = $rrp; + $this->variantsCount = $variantsCount; + $this->hasVariants = $hasVariants; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['id'], + (int) $data['product_id'], + (string) $data['title'], + new StatusEnum((string) $data[StatusEnum::KEY_NAME]), + new ProductStageEnum((string) $data[ProductStageEnum::KEY_NAME]), + (int) $data['in_stock'], + (string) $data['category_id'], + (float) $data['price'], + (float) $data['purchase_price'], + (float) $data['rrp'], + (int) $data['variants_count'], + (bool) $data['has_variants'], + ); + } + + public function getId(): string + { + return $this->id; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function getStage(): ProductStageEnum + { + return $this->stage; + } + + public function getInStock(): int + { + return $this->inStock; + } + + public function getCategoryId(): string + { + return $this->categoryId; + } + + public function getPrice(): float + { + return $this->price; + } + + public function getPurchasePrice(): float + { + return $this->purchasePrice; + } + + public function getRrp(): float + { + return $this->rrp; + } + + public function getVariantsCount(): int + { + return $this->variantsCount; + } + + public function hasVariants(): bool + { + return $this->hasVariants; + } + +} diff --git a/src/Article/Entity/BasicProductList.php b/src/Article/Entity/BasicProductList.php new file mode 100644 index 0000000..3678600 --- /dev/null +++ b/src/Article/Entity/BasicProductList.php @@ -0,0 +1,45 @@ + + * @property BasicProduct[] $data + */ +final class BasicProductList extends AbstractList +{ + + public static function createWithCallback(Closure $callback, Filter $filter): self + { + return new self($callback, $filter); + } + + /** + * @return false|BasicProduct + */ + public function current() + { + return current($this->data); + } + + /** + * @param array> $data + * @return BasicProduct[] + * @throws Exception + */ + protected function parseData(array $data): array + { + $items = []; + foreach ($data as $item) { + $items[] = BasicProduct::createFromApi($item); + } + + return $items; + } + +} diff --git a/src/Article/Entity/BasicVariant.php b/src/Article/Entity/BasicVariant.php new file mode 100644 index 0000000..1428014 --- /dev/null +++ b/src/Article/Entity/BasicVariant.php @@ -0,0 +1,113 @@ +id = $id; + $this->productId = $productId; + $this->variantId = $variantId; + $this->title = $title; + $this->status = $status; + $this->inStock = $inStock; + $this->price = $price; + $this->purchasePrice = $purchasePrice; + $this->rrp = $rrp; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['id'], + (int) $data['product_id'], + (int) $data['variant_id'], + (string) $data['title'], + new StatusEnum((string) $data[StatusEnum::KEY_NAME]), + (int) $data['in_stock'], + (float) $data['price'], + (float) $data['purchase_price'], + (float) $data['rrp'], + ); + } + + public function getId(): string + { + return $this->id; + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getVariantId(): int + { + return $this->variantId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function getInStock(): int + { + return $this->inStock; + } + + public function getPrice(): float + { + return $this->price; + } + + public function getPurchasePrice(): float + { + return $this->purchasePrice; + } + + public function getRrp(): float + { + return $this->rrp; + } + +} diff --git a/src/Article/Entity/BasicVariantList.php b/src/Article/Entity/BasicVariantList.php new file mode 100644 index 0000000..8d1b420 --- /dev/null +++ b/src/Article/Entity/BasicVariantList.php @@ -0,0 +1,45 @@ + + * @property BasicVariant[] $data + */ +final class BasicVariantList extends AbstractList +{ + + public static function createWithCallback(Closure $callback, Filter $filter): self + { + return new self($callback, $filter); + } + + /** + * @return false|BasicVariant + */ + public function current() + { + return current($this->data); + } + + /** + * @param array> $data + * @return BasicVariant[] + * @throws Exception + */ + protected function parseData(array $data): array + { + $items = []; + foreach ($data as $item) { + $items[] = BasicVariant::createFromApi($item); + } + + return $items; + } + +} diff --git a/src/Article/Entity/Common/AbstractArticle.php b/src/Article/Entity/Common/AbstractArticle.php new file mode 100644 index 0000000..607cdd4 --- /dev/null +++ b/src/Article/Entity/Common/AbstractArticle.php @@ -0,0 +1,240 @@ +id = $id; + $this->articleId = $articleId; + $this->title = $title; + $this->url = $url; + $this->shortDesc = $shortDesc; + $this->longDesc = $longDesc; + $this->priority = $priority; + $this->barcode = $barcode; + $this->price = $price; + $this->purchasePrice = $purchasePrice; + $this->rrp = $rrp; + $this->media = $media; + $this->promotions = $promotions; + $this->parameters = $parameters; + $this->dimensions = $dimensions; + $this->availability = $availability; + $this->labels = $labels; + $this->overrides = $overrides; + $this->recommended = $recommended; + $this->deliveryDelay = $deliveryDelay; + $this->freeDelivery = $freeDelivery; + $this->packageSize = $packageSize; + $this->mallboxAllowed = $mallboxAllowed; + } + + /** + * @param array $data + * + * @internal + */ + abstract public static function createFromApi(array $data): self; + + public function getId(): string + { + return $this->id; + } + + public function getArticleId(): int + { + return $this->articleId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getUrl(): string + { + return $this->url; + } + + public function getShortDesc(): string + { + return $this->shortDesc; + } + + public function getLongDesc(): string + { + return $this->longDesc; + } + + public function getPriority(): int + { + return $this->priority; + } + + public function getBarcode(): ?string + { + return $this->barcode; + } + + public function getPrice(): float + { + return $this->price; + } + + public function getPurchasePrice(): float + { + return $this->purchasePrice; + } + + public function getRrp(): float + { + return $this->rrp; + } + + public function getMedia(): MediaIterator + { + return $this->media; + } + + public function getPromotions(): PromotionIterator + { + return $this->promotions; + } + + public function getParameters(): ParameterIterator + { + return $this->parameters; + } + + public function getDimensions(): Dimensions + { + return $this->dimensions; + } + + public function getAvailability(): Availability + { + return $this->availability; + } + + public function getLabels(): LabelIterator + { + return $this->labels; + } + + public function getOverrides(): OverrideIterator + { + return $this->overrides; + } + + /** + * @return string[] + */ + public function getRecommended(): array + { + return $this->recommended; + } + + public function getDeliveryDelay(): int + { + return $this->deliveryDelay; + } + + public function hasFreeDelivery(): bool + { + return $this->freeDelivery; + } + + public function getPackageSize(): PackageSizeEnum + { + return $this->packageSize; + } + + public function hasMallboxAllowed(): bool + { + return $this->mallboxAllowed; + } + +} diff --git a/src/Article/Entity/Common/Availability.php b/src/Article/Entity/Common/Availability.php new file mode 100644 index 0000000..a11fb9b --- /dev/null +++ b/src/Article/Entity/Common/Availability.php @@ -0,0 +1,66 @@ +status = $status; + $this->inStock = $inStock; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + new StatusEnum((string) $data[StatusEnum::KEY_NAME]), + (int) $data['in_stock'], + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + StatusEnum::KEY_NAME => $this->getStatus()->getValue(), + 'in_stock' => $this->getInStock(), + ]; + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function setStatus(StatusEnum $status): void + { + $this->status = $status; + } + + public function getInStock(): int + { + return $this->inStock; + } + + public function setInStock(int $inStock): void + { + $this->inStock = $inStock; + } + +} diff --git a/src/Article/Entity/Common/Dimensions.php b/src/Article/Entity/Common/Dimensions.php new file mode 100644 index 0000000..2d6dc14 --- /dev/null +++ b/src/Article/Entity/Common/Dimensions.php @@ -0,0 +1,94 @@ +weight = $weight; + $this->width = $width; + $this->height = $height; + $this->length = $length; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (float) $data['weight'], + (float) $data['width'], + (float) $data['height'], + (float) $data['length'], + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'weight' => $this->getWeight(), + 'width' => $this->getWidth(), + 'height' => $this->getHeight(), + 'length' => $this->getLength(), + ]; + } + + public function getWeight(): float + { + return $this->weight; + } + + public function setWeight(float $weight): void + { + $this->weight = $weight; + } + + public function getWidth(): float + { + return $this->width; + } + + public function setWidth(float $width): void + { + $this->width = $width; + } + + public function getHeight(): float + { + return $this->height; + } + + public function setHeight(float $height): void + { + $this->height = $height; + } + + public function getLength(): float + { + return $this->length; + } + + public function setLength(float $length): void + { + $this->length = $length; + } + +} diff --git a/src/Article/Entity/Common/Label.php b/src/Article/Entity/Common/Label.php new file mode 100644 index 0000000..78a1067 --- /dev/null +++ b/src/Article/Entity/Common/Label.php @@ -0,0 +1,85 @@ +label = $label; + $this->from = $from; + $this->to = $to; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['label'], + new DateTime($data['from']), + new DateTime($data['to']), + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'label' => $this->getLabel(), + 'from' => $this->getFrom()->format(InputDataUtil::DATE_TIME_FORMAT), + 'to' => $this->getTo()->format(InputDataUtil::DATE_TIME_FORMAT), + ]; + } + + public function getLabel(): string + { + return $this->label; + } + + public function setLabel(string $label): void + { + $this->label = $label; + } + + public function getFrom(): DateTimeInterface + { + return $this->from; + } + + public function setFrom(DateTimeInterface $from): void + { + $this->from = $from; + } + + public function getTo(): DateTimeInterface + { + return $this->to; + } + + public function setTo(DateTimeInterface $to): void + { + $this->to = $to; + } + +} diff --git a/src/Article/Entity/Common/LabelIterator.php b/src/Article/Entity/Common/LabelIterator.php new file mode 100644 index 0000000..e80d913 --- /dev/null +++ b/src/Article/Entity/Common/LabelIterator.php @@ -0,0 +1,66 @@ + + * @property array $data + */ +final class LabelIterator extends AbstractStringKeyIterator +{ + + public function __construct(Label ...$data) + { + foreach ($data as $availability) { + $this->data[$availability->getLabel()] = $availability; + } + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Label => Label::createFromApi($item), $data) + ); + } + + /** + * @return array> + */ + public function getArrayForApi(): array + { + return array_map(fn(Label $label): array => $label->getArrayForApi(), array_values($this->data)); + } + + /** + * @return false|Label + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Label + { + return $this->data[$key] ?? null; + } + + public function remove(string $key): void + { + parent::remove($key); + } + + public function add(Label $value): void + { + $this->data[$value->getLabel()] = $value; + } + +} diff --git a/src/Article/Entity/Common/Media.php b/src/Article/Entity/Common/Media.php new file mode 100644 index 0000000..c7048d8 --- /dev/null +++ b/src/Article/Entity/Common/Media.php @@ -0,0 +1,109 @@ +url = $url; + $this->main = $main; + $this->switch = $switch; + $this->energyLabel = $energyLabel; + $this->informationList = $informationList; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['url'], + (bool) $data['main'], + InputDataUtil::getNullableString($data, 'switch'), + (bool) $data['energy_label'], + (bool) $data['information_list'], + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'url' => $this->getUrl(), + 'main' => $this->isMain(), + 'switch' => $this->getSwitch(), + 'energy_label' => $this->isEnergyLabel(), + 'information_list' => $this->isInformationList(), + ]; + } + + public function getUrl(): string + { + return $this->url; + } + + public function setUrl(string $url): void + { + $this->url = $url; + } + + public function isMain(): bool + { + return $this->main; + } + + public function setMain(bool $main): void + { + $this->main = $main; + } + + public function getSwitch(): ?string + { + return $this->switch; + } + + public function setSwitch(?string $switch): void + { + $this->switch = $switch; + } + + public function isEnergyLabel(): bool + { + return $this->energyLabel; + } + + public function setEnergyLabel(bool $energyLabel): void + { + $this->energyLabel = $energyLabel; + } + + public function isInformationList(): bool + { + return $this->informationList; + } + + public function setInformationList(bool $informationList): void + { + $this->informationList = $informationList; + } + +} diff --git a/src/Article/Entity/Common/MediaIterator.php b/src/Article/Entity/Common/MediaIterator.php new file mode 100644 index 0000000..c41ca54 --- /dev/null +++ b/src/Article/Entity/Common/MediaIterator.php @@ -0,0 +1,67 @@ + + * @property Media[] $data + */ +final class MediaIterator extends AbstractIntKeyIterator +{ + + public function __construct(Media ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Media => Media::createFromApi($item), $data) + ); + } + + /** + * @return array> + */ + public function getArrayForApi(): array + { + return array_map(fn(Media $media): array => $media->getArrayForApi(), array_values($this->data)); + } + + /** + * @return false|Media + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?Media + { + return $this->data[$key] ?? null; + } + + public function remove(int $key): void + { + parent::remove($key); + } + + public function removeByUrl(string $url): void + { + $this->data = array_filter($this->data, fn(Media $media): bool => $media->getUrl() !== $url); + } + + public function add(Media $value): void + { + $this->data[] = $value; + } + +} diff --git a/src/Article/Entity/Common/Override.php b/src/Article/Entity/Common/Override.php new file mode 100644 index 0000000..612a904 --- /dev/null +++ b/src/Article/Entity/Common/Override.php @@ -0,0 +1,65 @@ +type = $type; + $this->value = $value; + $this->validFrom = $validFrom; + $this->validTo = $validTo; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(string $type, array $data): self + { + return new self( + $type, + (string) $data['value'], + new DateTime($data['valid_from']), + new DateTime($data['valid_to']), + ); + } + + public function getType(): string + { + return $this->type; + } + + public function getValue(): string + { + return $this->value; + } + + public function getValidFrom(): DateTimeInterface + { + return $this->validFrom; + } + + public function getValidTo(): DateTimeInterface + { + return $this->validTo; + } + +} diff --git a/src/Article/Entity/Common/OverrideIterator.php b/src/Article/Entity/Common/OverrideIterator.php new file mode 100644 index 0000000..519b552 --- /dev/null +++ b/src/Article/Entity/Common/OverrideIterator.php @@ -0,0 +1,58 @@ + + * @property Override[] $data + */ +final class OverrideIterator extends AbstractIntKeyIterator +{ + + private function __construct(Override ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + $items = []; + foreach ($data as $type => $values) { + foreach ($values as $value) { + $items[] = Override::createFromApi($type, $value); + } + } + + return new self(...$items); + } + + /** + * @return false|Override + */ + public function current() + { + return current($this->data); + } + + public function filterByType(string $type): self + { + return new self( + ...array_filter($this->data, fn(Override $override): bool => $override->getType() !== $type) + ); + } + + public function get(int $key): ?Override + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Article/Entity/Common/PackageSizeEnum.php b/src/Article/Entity/Common/PackageSizeEnum.php new file mode 100644 index 0000000..b746fda --- /dev/null +++ b/src/Article/Entity/Common/PackageSizeEnum.php @@ -0,0 +1,24 @@ +id = $id; + $this->values = $values; + } + + public static function create(string $id, string ...$values): self + { + return new self($id, $values); + } + + /** + * @param string[] $values + * + * @internal + */ + public static function createFromApi(string $id, array $values): self + { + return new self($id, $values); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [$this->getId() => $this->getValues()]; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + /** + * @return string[] + */ + public function getValues(): array + { + return $this->values; + } + + /** + * @param string ...$values + */ + public function setValues(string ...$values): void + { + $this->values = $values; + } + +} diff --git a/src/Article/Entity/Common/ParameterIterator.php b/src/Article/Entity/Common/ParameterIterator.php new file mode 100644 index 0000000..931fc37 --- /dev/null +++ b/src/Article/Entity/Common/ParameterIterator.php @@ -0,0 +1,72 @@ + + * @property array $data + */ +final class ParameterIterator extends AbstractStringKeyIterator +{ + + public function __construct(Parameter ...$data) + { + foreach ($data as $parameter) { + $this->data[$parameter->getId()] = $parameter; + } + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + $items = []; + foreach ($data as $id => $values) { + $items[] = Parameter::createFromApi($id, $values); + } + + return new self(...$items); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + $out = []; + foreach ($this->data as $parameter) { + $out[$parameter->getId()] = $parameter->getValues(); + } + + return $out; + } + + /** + * @return false|Parameter + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Parameter + { + return $this->data[$key] ?? null; + } + + public function remove(string $key): void + { + parent::remove($key); + } + + public function add(Parameter $value): void + { + $this->data[$value->getId()] = $value; + } + +} diff --git a/src/Article/Entity/Common/Pricing.php b/src/Article/Entity/Common/Pricing.php new file mode 100644 index 0000000..31177a6 --- /dev/null +++ b/src/Article/Entity/Common/Pricing.php @@ -0,0 +1,65 @@ +price = $price; + $this->rrp = $rrp; + $this->purchasePrice = $purchasePrice; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (float) $data['price'], + (float) $data['rrp'], + (float) $data['purchase_price'], + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'price' => $this->getPrice(), + 'rrp' => $this->getRrp(), + 'purchase_price' => $this->getPurchasePrice(), + ]; + } + + public function getPrice(): float + { + return $this->price; + } + + public function getRrp(): float + { + return $this->rrp; + } + + public function getPurchasePrice(): float + { + return $this->purchasePrice; + } + +} diff --git a/src/Article/Entity/Common/Promotion.php b/src/Article/Entity/Common/Promotion.php new file mode 100644 index 0000000..deb7024 --- /dev/null +++ b/src/Article/Entity/Common/Promotion.php @@ -0,0 +1,93 @@ +price = $price; + $this->from = $from; + $this->to = $to; + $this->source = $source; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (float) $data['price'], + new DateTime($data['from']), + new DateTime($data['to']), + (string) $data['source'], + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'price' => $this->getPrice(), + 'from' => $this->getFrom()->format(InputDataUtil::DATE_TIME_FORMAT), + 'to' => $this->getTo()->format(InputDataUtil::DATE_TIME_FORMAT), + ]; + } + + public function getPrice(): float + { + return $this->price; + } + + public function setPrice(float $price): void + { + $this->price = $price; + } + + public function getFrom(): DateTimeInterface + { + return $this->from; + } + + public function setFrom(DateTimeInterface $from): void + { + $this->from = $from; + } + + public function getTo(): DateTimeInterface + { + return $this->to; + } + + public function setTo(DateTimeInterface $to): void + { + $this->to = $to; + } + + public function getSource(): string + { + return $this->source; + } + +} diff --git a/src/Article/Entity/Common/PromotionIterator.php b/src/Article/Entity/Common/PromotionIterator.php new file mode 100644 index 0000000..b500efb --- /dev/null +++ b/src/Article/Entity/Common/PromotionIterator.php @@ -0,0 +1,54 @@ + + * @property Promotion[] $data + */ +final class PromotionIterator extends AbstractIntKeyIterator +{ + + public function __construct(Promotion ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Promotion => Promotion::createFromApi($item), $data) + ); + } + + /** + * @return array> + */ + public function getArrayForApi(): array + { + return array_map(fn(Promotion $promotion): array => $promotion->getArrayForApi(), array_values($this->data)); + } + + /** + * @return false|Promotion + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?Promotion + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Article/Entity/Common/StatusEnum.php b/src/Article/Entity/Common/StatusEnum.php new file mode 100644 index 0000000..71ec717 --- /dev/null +++ b/src/Article/Entity/Common/StatusEnum.php @@ -0,0 +1,27 @@ +stage = $stage; + $this->categoryId = $categoryId; + $this->vat = $vat; + $this->variants = $variants; + $this->variableParameters = $variableParameters; + $this->partnerTitle = $partnerTitle; + $this->brandId = $brandId; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['id'], + (int) $data['article_id'], + new ProductStageEnum((string) $data[ProductStageEnum::KEY_NAME]), + (string) $data['title'], + (string) $data['url'], + (string) $data['shortdesc'], + (string) $data['longdesc'], + (string) $data['category_id'], + (int) $data['priority'], + InputDataUtil::getNullableString($data, 'barcode'), + (float) $data['price'], + (float) $data['purchase_price'], + (int) $data['vat'], + (float) $data['rrp'], + MediaIterator::createFromApi($data['media']), + PromotionIterator::createFromApi($data['promotions']), + VariantIterator::createFromApi($data['variants']), + ParameterIterator::createFromApi($data['parameters']), + $data['variable_parameters'], + Dimensions::createFromApi($data['dimensions']), + Availability::createFromApi($data['availability']), + LabelIterator::createFromApi($data['labels']), + OverrideIterator::createFromApi($data['overrides']), + $data['recommended'], + (int) $data['delivery_delay'], + (bool) $data['free_delivery'], + new PackageSizeEnum($data['package_size']), + (bool) $data['mallbox_allowed'], + InputDataUtil::getNullableString($data, 'partner_title'), + InputDataUtil::getNullableString($data, 'brand_id'), + ); + } + + public function getStage(): ProductStageEnum + { + return $this->stage; + } + + public function getCategoryId(): string + { + return $this->categoryId; + } + + public function getVat(): int + { + return $this->vat; + } + + public function getVariants(): VariantIterator + { + return $this->variants; + } + + /** + * @return string[] + */ + public function getVariableParameters(): array + { + return $this->variableParameters; + } + + public function getPartnerTitle(): ?string + { + return $this->partnerTitle; + } + + public function getBrandId(): ?string + { + return $this->brandId; + } + +} diff --git a/src/Article/Entity/ProductStageEnum.php b/src/Article/Entity/ProductStageEnum.php new file mode 100644 index 0000000..329cca7 --- /dev/null +++ b/src/Article/Entity/ProductStageEnum.php @@ -0,0 +1,24 @@ + $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['id'], + (int) $data['article_id'], + (string) $data['title'], + (string) $data['url'], + (string) $data['shortdesc'], + (string) $data['longdesc'], + (int) $data['priority'], + InputDataUtil::getNullableString($data, 'barcode'), + (float) $data['price'], + (float) $data['purchase_price'], + (float) $data['rrp'], + MediaIterator::createFromApi($data['media']), + PromotionIterator::createFromApi($data['promotions']), + ParameterIterator::createFromApi($data['parameters']), + Dimensions::createFromApi($data['dimensions']), + Availability::createFromApi($data['availability']), + LabelIterator::createFromApi($data['labels']), + OverrideIterator::createFromApi($data['overrides']), + $data['recommended'], + (int) $data['delivery_delay'], + (bool) $data['free_delivery'], + new PackageSizeEnum($data['package_size']), + (bool) $data['mallbox_allowed'], + ); + } + +} diff --git a/src/Article/Entity/VariantIterator.php b/src/Article/Entity/VariantIterator.php new file mode 100644 index 0000000..9cec14d --- /dev/null +++ b/src/Article/Entity/VariantIterator.php @@ -0,0 +1,48 @@ + + * @property array $data + */ +final class VariantIterator extends AbstractStringKeyIterator +{ + + public function __construct(Variant ...$data) + { + foreach ($data as $variant) { + $this->data[$variant->getId()] = $variant; + } + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Variant => Variant::createFromApi($item), $data) + ); + } + + /** + * @return false|Variant + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Variant + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Brand/BrandClient.php b/src/Brand/BrandClient.php new file mode 100644 index 0000000..067324c --- /dev/null +++ b/src/Brand/BrandClient.php @@ -0,0 +1,19 @@ +sendJson('GET', self::LIST)['data']); + } + +} diff --git a/src/Brand/Entity/Brand.php b/src/Brand/Entity/Brand.php new file mode 100644 index 0000000..3aee051 --- /dev/null +++ b/src/Brand/Entity/Brand.php @@ -0,0 +1,45 @@ +brandId = $brandId; + $this->title = $title; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['brand_id'], + (string) $data['title'], + ); + } + + public function getBrandId(): string + { + return $this->brandId; + } + + public function getTitle(): string + { + return $this->title; + } + +} diff --git a/src/Brand/Entity/BrandIterator.php b/src/Brand/Entity/BrandIterator.php new file mode 100644 index 0000000..3d2110f --- /dev/null +++ b/src/Brand/Entity/BrandIterator.php @@ -0,0 +1,46 @@ + + * @property array $data + */ +final class BrandIterator extends AbstractStringKeyIterator +{ + + private function __construct(Brand ...$data) + { + foreach ($data as $brand) { + $this->data[$brand->getBrandId()] = $brand; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Brand => Brand::createFromApi($item), $data) + ); + } + + /** + * @return false|Brand + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Brand + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/CategoryClient.php b/src/Category/CategoryClient.php new file mode 100644 index 0000000..9bbc981 --- /dev/null +++ b/src/Category/CategoryClient.php @@ -0,0 +1,36 @@ +sendJson('GET', self::LIST)['data']); + } + + public function getParameters(string $categoryId): ParameterIterator + { + return ParameterIterator::createFromApi($this->sendJson('GET', sprintf(self::PARAMETERS, $categoryId))['data']); + } + + public function tree(ShopIdEnum $shopId): TreeItemIterator + { + return TreeItemIterator::createFromMixedApi( + $this->sendJson('GET', sprintf(self::TREE, $shopId->getValue()))['data'] + ); + } + +} diff --git a/src/Category/Entity/Category.php b/src/Category/Entity/Category.php new file mode 100644 index 0000000..62bfb1e --- /dev/null +++ b/src/Category/Entity/Category.php @@ -0,0 +1,45 @@ +categoryId = $categoryId; + $this->title = $title; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['category_id'], + (string) $data['title'], + ); + } + + public function getCategoryId(): string + { + return $this->categoryId; + } + + public function getTitle(): string + { + return $this->title; + } + +} diff --git a/src/Category/Entity/CategoryIterator.php b/src/Category/Entity/CategoryIterator.php new file mode 100644 index 0000000..1a54166 --- /dev/null +++ b/src/Category/Entity/CategoryIterator.php @@ -0,0 +1,46 @@ + + * @property array $data + */ +final class CategoryIterator extends AbstractStringKeyIterator +{ + + private function __construct(Category ...$data) + { + foreach ($data as $category) { + $this->data[$category->getCategoryId()] = $category; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Category => Category::createFromApi($item), $data) + ); + } + + /** + * @return false|Category + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Category + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/Entity/Parameter.php b/src/Category/Entity/Parameter.php new file mode 100644 index 0000000..3840c4e --- /dev/null +++ b/src/Category/Entity/Parameter.php @@ -0,0 +1,66 @@ +paramId = $paramId; + $this->title = $title; + $this->unit = $unit; + $this->values = $values; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['param_id'], + (string) $data['title'], + (string) $data['unit'], + ParameterValueIterator::createFromApi($data['values']), + ); + } + + public function getParamId(): string + { + return $this->paramId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getUnit(): string + { + return $this->unit; + } + + public function hasUnit(): bool + { + return $this->unit !== ''; + } + + public function getValues(): ParameterValueIterator + { + return $this->values; + } + +} diff --git a/src/Category/Entity/ParameterIterator.php b/src/Category/Entity/ParameterIterator.php new file mode 100644 index 0000000..5635de7 --- /dev/null +++ b/src/Category/Entity/ParameterIterator.php @@ -0,0 +1,46 @@ + + * @property array $data + */ +final class ParameterIterator extends AbstractStringKeyIterator +{ + + private function __construct(Parameter ...$data) + { + foreach ($data as $category) { + $this->data[$category->getParamId()] = $category; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Parameter => Parameter::createFromApi($item), $data) + ); + } + + /** + * @return false|Parameter + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Parameter + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/Entity/ParameterValue.php b/src/Category/Entity/ParameterValue.php new file mode 100644 index 0000000..92c6b6d --- /dev/null +++ b/src/Category/Entity/ParameterValue.php @@ -0,0 +1,45 @@ +value = $id; + $this->text = $text; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['value'], + (string) $data['text'], + ); + } + + public function getValue(): string + { + return $this->value; + } + + public function getText(): string + { + return $this->text; + } + +} diff --git a/src/Category/Entity/ParameterValueIterator.php b/src/Category/Entity/ParameterValueIterator.php new file mode 100644 index 0000000..346e3b4 --- /dev/null +++ b/src/Category/Entity/ParameterValueIterator.php @@ -0,0 +1,46 @@ + + * @property array $data + */ +final class ParameterValueIterator extends AbstractStringKeyIterator +{ + + private function __construct(ParameterValue ...$data) + { + foreach ($data as $value) { + $this->data[$value->getValue()] = $value; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): ParameterValue => ParameterValue::createFromApi($item), $data) + ); + } + + /** + * @return false|ParameterValue + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?ParameterValue + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/Entity/TreeItem.php b/src/Category/Entity/TreeItem.php new file mode 100644 index 0000000..8edf310 --- /dev/null +++ b/src/Category/Entity/TreeItem.php @@ -0,0 +1,88 @@ +title = $title; + $this->categoryVisible = $categoryVisible; + $this->items = $items; + $this->menuItems = $menuItems; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['title'], + (bool) $data['categoryVisible'], + TreeItemIterator::createFromApi($data['items']), + TreeMenuItemIterator::createFromApi($data['menuItems']), + ); + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromMixedApi(array $data): self + { + // Ugly hack, because the API does not return a valid tree object (mixes 2 objects into same list) + // -> new endpoint that returns valid data should be created + $items = []; + $menuItems = []; + foreach ($data['items'] ?? [] as $item) { + if (isset($item['menuItemId'])) { + $menuItems[] = $item; + } else { + $items[] = $item; + } + } + + return new self( + (string) $data['title'], + (bool) $data['categoryVisible'], + TreeItemIterator::createFromMixedApi($items), + TreeMenuItemIterator::createFromApi($menuItems), + ); + } + + public function getTitle(): string + { + return $this->title; + } + + public function isCategoryVisible(): bool + { + return $this->categoryVisible; + } + + public function getItems(): TreeItemIterator + { + return $this->items; + } + + public function getMenuItems(): TreeMenuItemIterator + { + return $this->menuItems; + } + +} diff --git a/src/Category/Entity/TreeItemIterator.php b/src/Category/Entity/TreeItemIterator.php new file mode 100644 index 0000000..4151bd7 --- /dev/null +++ b/src/Category/Entity/TreeItemIterator.php @@ -0,0 +1,56 @@ + + * @property TreeItem[] $data + */ +final class TreeItemIterator extends AbstractIntKeyIterator +{ + + private function __construct(TreeItem ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): TreeItem => TreeItem::createFromApi($item), $data) + ); + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromMixedApi(array $data): self + { + return new self( + ...array_map(fn(array $item): TreeItem => TreeItem::createFromMixedApi($item), $data) + ); + } + + /** + * @return false|TreeItem + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?TreeItem + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/Entity/TreeMenuConstraint.php b/src/Category/Entity/TreeMenuConstraint.php new file mode 100644 index 0000000..724b4d0 --- /dev/null +++ b/src/Category/Entity/TreeMenuConstraint.php @@ -0,0 +1,70 @@ +paramId = $paramId; + $this->operator = $operator; + $this->value1 = $value1; + $this->value2 = $value2; + $this->class = $class; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['paramId'], + (string) $data['operator'], + (string) $data['value1'], + InputDataUtil::getNullableString($data, 'value2'), + (int) $data['class'], + ); + } + + public function getParamId(): string + { + return $this->paramId; + } + + public function getOperator(): string + { + return $this->operator; + } + + public function getValue1(): string + { + return $this->value1; + } + + public function getValue2(): ?string + { + return $this->value2; + } + + public function getClass(): int + { + return $this->class; + } + +} diff --git a/src/Category/Entity/TreeMenuConstraintIterator.php b/src/Category/Entity/TreeMenuConstraintIterator.php new file mode 100644 index 0000000..dcc4657 --- /dev/null +++ b/src/Category/Entity/TreeMenuConstraintIterator.php @@ -0,0 +1,44 @@ + + * @property TreeMenuConstraint[] $data + */ +final class TreeMenuConstraintIterator extends AbstractIntKeyIterator +{ + + private function __construct(TreeMenuConstraint ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): TreeMenuConstraint => TreeMenuConstraint::createFromApi($item), $data) + ); + } + + /** + * @return false|TreeMenuConstraint + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?TreeMenuConstraint + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/Entity/TreeMenuItem.php b/src/Category/Entity/TreeMenuItem.php new file mode 100644 index 0000000..1c7fa0c --- /dev/null +++ b/src/Category/Entity/TreeMenuItem.php @@ -0,0 +1,69 @@ +menuItemId = $menuItemId; + $this->title = $title; + $this->categoryVisible = $categoryVisible; + $this->sapCategories = $sapCategories; + $this->url = $url; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['menuItemId'], + (string) $data['title'], + (bool) $data['categoryVisible'], + TreeSapCategoryIterator::createFromApi($data['sapCategories']), + (string) $data['url'], + ); + } + + public function getMenuItemId(): int + { + return $this->menuItemId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function isCategoryVisible(): bool + { + return $this->categoryVisible; + } + + public function getSapCategories(): TreeSapCategoryIterator + { + return $this->sapCategories; + } + + public function getUrl(): string + { + return $this->url; + } + +} diff --git a/src/Category/Entity/TreeMenuItemIterator.php b/src/Category/Entity/TreeMenuItemIterator.php new file mode 100644 index 0000000..38028d6 --- /dev/null +++ b/src/Category/Entity/TreeMenuItemIterator.php @@ -0,0 +1,44 @@ + + * @property TreeMenuItem[] $data + */ +final class TreeMenuItemIterator extends AbstractIntKeyIterator +{ + + private function __construct(TreeMenuItem ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): TreeMenuItem => TreeMenuItem::createFromApi($item), $data) + ); + } + + /** + * @return false|TreeMenuItem + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?TreeMenuItem + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Category/Entity/TreeSapCategory.php b/src/Category/Entity/TreeSapCategory.php new file mode 100644 index 0000000..25fe741 --- /dev/null +++ b/src/Category/Entity/TreeSapCategory.php @@ -0,0 +1,61 @@ +segment = $segment; + $this->productTypeId = $productTypeId; + $this->operator = $operator; + $this->menuConstraints = $menuConstraints; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['segment'], + (string) $data['productTypeId'], + (string) $data['operator'], + TreeMenuConstraintIterator::createFromApi($data['menuConstraints']), + ); + } + + public function getSegment(): string + { + return $this->segment; + } + + public function getProductTypeId(): string + { + return $this->productTypeId; + } + + public function getOperator(): string + { + return $this->operator; + } + + public function getMenuConstraints(): TreeMenuConstraintIterator + { + return $this->menuConstraints; + } + +} diff --git a/src/Category/Entity/TreeSapCategoryIterator.php b/src/Category/Entity/TreeSapCategoryIterator.php new file mode 100644 index 0000000..f9e9371 --- /dev/null +++ b/src/Category/Entity/TreeSapCategoryIterator.php @@ -0,0 +1,44 @@ + + * @property TreeSapCategory[] $data + */ +final class TreeSapCategoryIterator extends AbstractIntKeyIterator +{ + + private function __construct(TreeSapCategory ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): TreeSapCategory => TreeSapCategory::createFromApi($item), $data) + ); + } + + /** + * @return false|TreeSapCategory + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?TreeSapCategory + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Checks/ChecksClient.php b/src/Checks/ChecksClient.php new file mode 100644 index 0000000..bcc87c7 --- /dev/null +++ b/src/Checks/ChecksClient.php @@ -0,0 +1,25 @@ +sendJson('GET', self::MEDIA)['errors']); + } + + public function getDeliveryErrors(): ErrorIterator + { + return ErrorIterator::createFromApi($this->sendJson('GET', self::DELIVERIES)['errors']); + } + +} diff --git a/src/Checks/Entity/Error.php b/src/Checks/Entity/Error.php new file mode 100644 index 0000000..b8c15b0 --- /dev/null +++ b/src/Checks/Entity/Error.php @@ -0,0 +1,82 @@ +code = $code; + $this->attribute = $attribute; + $this->value = $value; + $this->msg = $msg; + $this->articles = $articles; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['code'], + (string) $data['attribute'], + (string) $data['value'], + (string) $data['msg'], + $data['articles'] ?? [], + ); + } + + public function getCode(): string + { + return $this->code; + } + + public function getAttribute(): string + { + return $this->attribute; + } + + public function getValue(): string + { + return $this->value; + } + + public function getMsg(): string + { + return $this->msg; + } + + /** + * @return string[] + */ + public function getArticles(): array + { + return $this->articles; + } + +} diff --git a/src/Checks/Entity/ErrorIterator.php b/src/Checks/Entity/ErrorIterator.php new file mode 100644 index 0000000..0850345 --- /dev/null +++ b/src/Checks/Entity/ErrorIterator.php @@ -0,0 +1,44 @@ + + * @property Error[] $data + */ +final class ErrorIterator extends AbstractIntKeyIterator +{ + + private function __construct(Error ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Error => Error::createFromApi($item), $data) + ); + } + + /** + * @return false|Error + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?Error + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Common/AbstractMpApiClient.php b/src/Common/AbstractMpApiClient.php new file mode 100644 index 0000000..b5bbfb3 --- /dev/null +++ b/src/Common/AbstractMpApiClient.php @@ -0,0 +1,103 @@ +client = $client; + $this->appTag = $appTag; + } + + /** + * @return array + */ + protected function getAppHeaders(): array + { + return [ + 'X-Application-Name' => MpApiClient::APP_NAME, + 'X-Application-Version' => MpApiClient::APP_VERSION, + 'X-Application-Tag' => $this->appTag, + ]; + } + + /** + * @param string $url + * @param array $query + * @param array $headers + * @return array + * @throws MpApiException + */ + protected function sendQueryRequest(string $url, array $query, array $headers = []): array + { + $url = sprintf('%s?%s', $url, http_build_query($query)); + + return $this->sendJson('GET', $url, [], $headers); + } + + /** + * @param string $method + * @param string $url + * @param array $content + * @param array $headers + * @param array $options + * @return array + * @throws MpApiException + */ + protected function sendJson(string $method, string $url, array $content = [], array $headers = [], array $options = []): array + { + // Add correct JSON request headers and app headers to request + $headers = array_merge( + [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + $this->getAppHeaders(), + $headers, + ); + + $response = $this->send( + new Request( + $method, + $url, + $headers, + (string) json_encode($content), + ), + $options + ); + + return json_decode($response->getBody()->getContents() ?: '{}', true); + } + + /** + * @param RequestInterface $request + * @param array $options + * @return ResponseInterface + * @throws MpApiException + */ + protected function send(RequestInterface $request, array $options = []): ResponseInterface + { + try { + return $this->client->send($request, $options); + } catch (GuzzleException $e) { + throw ExceptionFactory::fromGuzzleException($e); + } + } + +} diff --git a/src/Common/Authenticators/ClientIdAuthenticator.php b/src/Common/Authenticators/ClientIdAuthenticator.php new file mode 100644 index 0000000..87967c8 --- /dev/null +++ b/src/Common/Authenticators/ClientIdAuthenticator.php @@ -0,0 +1,32 @@ +clientId = $clientId; + } + + public function getHandler(): callable + { + return Middleware::mapRequest(fn(RequestInterface $request): RequestInterface => $this->authenticateRequest($request)); + } + + public function authenticateRequest(RequestInterface $request): RequestInterface + { + return $request->withUri(Uri::withQueryValue($request->getUri(), self::CLIENT_ID_KEY, $this->clientId)); + } + +} diff --git a/src/Common/DTO/Paging.php b/src/Common/DTO/Paging.php new file mode 100644 index 0000000..f879bef --- /dev/null +++ b/src/Common/DTO/Paging.php @@ -0,0 +1,61 @@ +total = $total; + $this->pages = $pages; + $this->size = $size; + $this->page = $page; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['total'], + (int) $data['pages'], + (int) $data['size'], + (int) $data['page'], + ); + } + + public function getTotal(): int + { + return $this->total; + } + + public function getPages(): int + { + return $this->pages; + } + + public function getSize(): int + { + return $this->size; + } + + public function getPage(): int + { + return $this->page; + } + +} diff --git a/src/Common/Interfaces/ArticleClientInterface.php b/src/Common/Interfaces/ArticleClientInterface.php new file mode 100644 index 0000000..bfc6823 --- /dev/null +++ b/src/Common/Interfaces/ArticleClientInterface.php @@ -0,0 +1,128 @@ + + */ + protected array $data = []; + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = []; + /** @var JsonSerializable $item - PHPStorm does not support generics yet: https://youtrack.jetbrains.com/issue/WI-47158 */ + foreach ($this->data as $item) { + $data[] = $item->jsonSerialize(); + } + + return $data; + } + + public function isEmpty(): bool + { + return $this->data === []; + } + + public function count(): int + { + return count($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function key(): int + { + return (int) key($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } + + public function rewind(): void + { + reset($this->data); + } + + public function exists(int $key): bool + { + return isset($this->data[$key]); + } + + /** + * Returns object under provided key/offset if present or null + * @param int $key represents either objects unique primary key or offset/order in iterator data set + */ + abstract public function get(int $key): ?object; + + /** + * Protected because not every iterator should be mutable + * @param int $key represents either objects unique primary key or offset/order in iterator data set + */ + protected function remove(int $key): void + { + unset($this->data[$key]); + } + + // correct types are not enforceable by PHP, but child classes should implement this method if needed + // abstract public function add(object $value): void; + +} diff --git a/src/Common/Util/AbstractList.php b/src/Common/Util/AbstractList.php new file mode 100644 index 0000000..bd66dcf --- /dev/null +++ b/src/Common/Util/AbstractList.php @@ -0,0 +1,133 @@ +callback = $callback; + $this->filter = $filter; + $this->startingOffset = $filter->getOffset(); + + // always load first batch immediately + $this->loadData(); + } + + public function autoloadEnabled(): bool + { + return $this->autoload; + } + + public function enableAutoload(): void + { + $this->autoload = true; + } + + public function disableAutoload(): void + { + $this->autoload = false; + } + + public function getPaging(): Paging + { + return $this->paging; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = []; + // iterate itself, to return all data if autoload is enabled + /** @var JsonSerializable $item - PHPStorm does not support generics yet: https://youtrack.jetbrains.com/issue/WI-47158 */ + foreach ($this as $item) { + $data[] = $item->jsonSerialize(); + } + + return [ + 'paging' => $this->paging->jsonSerialize(), + 'data' => $data, + ]; + } + + public function next(): void + { + if (!next($this->data) && $this->autoloadEnabled()) { + $this->loadNextPage(); + } + } + + public function key(): int + { + return (int) key($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } + + public function rewind(): void + { + if (!$this->autoloadEnabled() || $this->filter->getOffset() === $this->startingOffset) { + reset($this->data); + + return; + } + + $this->filter->setOffset($this->startingOffset); + $this->loadData(); + } + + /** + * Method responsible for returning array of correctly initialized entities to iterate over + * @param array> $data + * @return TData[] + */ + abstract protected function parseData(array $data): array; + + private function loadNextPage(): void + { + $newOffset = $this->filter->getLimit() + $this->filter->getOffset(); + + // do nothing when we are at the end + if ($this->getPaging()->getTotal() <= $newOffset) { + return; + } + + // move to next page and load data + $this->filter->setOffset($newOffset); + $this->loadData(); + } + + private function loadData(): void + { + $data = ($this->callback)($this->filter); + $this->data = $this->parseData($data['data']); + $this->paging = Paging::createFromApi($data['paging']); + } + +} diff --git a/src/Common/Util/AbstractStringEnum.php b/src/Common/Util/AbstractStringEnum.php new file mode 100644 index 0000000..6b6f615 --- /dev/null +++ b/src/Common/Util/AbstractStringEnum.php @@ -0,0 +1,104 @@ +value = $value; + } + + /** + * Provides access to values using ::CONSTANT_NAME() interface. + * @param string $constantName + * @param array $arguments + * @return static + */ + public static function __callStatic(string $constantName, array $arguments): AbstractStringEnum + { + if (!defined(static::class . '::' . $constantName)) { + throw new Error( + sprintf('Call to undefined method %s::%s().', static::class, $constantName) + ); + } + + return new static(constant(static::class . '::' . $constantName)); + } + + public function __toString(): string + { + return $this->getValue(); + } + + public function jsonSerialize(): string + { + return $this->getValue(); + } + + public function getValue(): string + { + return $this->value; + } + + public function equals(AbstractStringEnum $enum): bool + { + return $this === $enum || (static::class === get_class($enum) && $this->getValue() === $enum->getValue()); + } + + public function equalsOneOf(AbstractStringEnum ...$enums): bool + { + foreach ($enums as $enum) { + if ($this === $enum || (static::class === get_class($enum) && $this->getValue() === $enum->getValue())) { + return true; + } + } + + return false; + } + + public function equalsValue(string $value): bool + { + if (!in_array($value, static::TYPES, true)) { + throw new InvalidArgumentException( + sprintf(static::INVALID_VALUE_MSG, $value, implode(', ', static::TYPES)) + ); + } + + return $this->getValue() === $value; + } + + public function equalsOneOfValues(string ...$values): bool + { + $invalidValues = array_diff($values, static::TYPES); + if ($invalidValues !== []) { + throw new InvalidArgumentException( + sprintf(static::INVALID_VALUE_MSG, implode(', ', $invalidValues), implode(', ', static::TYPES)) + ); + } + + return in_array($this->getValue(), $values, true); + } + +} diff --git a/src/Common/Util/AbstractStringKeyIterator.php b/src/Common/Util/AbstractStringKeyIterator.php new file mode 100644 index 0000000..28d3cd3 --- /dev/null +++ b/src/Common/Util/AbstractStringKeyIterator.php @@ -0,0 +1,87 @@ + + */ + protected array $data = []; + + /** + * @return array + */ + public function jsonSerialize(): array + { + $data = []; + /** @var JsonSerializable $item - PHPStorm does not support generics yet: https://youtrack.jetbrains.com/issue/WI-47158 */ + foreach ($this->data as $item) { + $data[] = $item->jsonSerialize(); + } + + return $data; + } + + public function isEmpty(): bool + { + return $this->data === []; + } + + public function count(): int + { + return count($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function key(): string + { + return (string) key($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } + + public function rewind(): void + { + reset($this->data); + } + + public function exists(string $key): bool + { + return isset($this->data[$key]); + } + + /** + * Returns object under provided key if present or null + */ + abstract public function get(string $key): ?object; + + /** + * Removes object with provided key if present + */ + protected function remove(string $key): void + { + // Protected because not every iterator should be mutable + unset($this->data[$key]); + } + + // correct types arent enforceable by PHP, but child classes should implement this method if needed + // abstract public function add(object $value): void; + +} diff --git a/src/Common/Util/DataTypeUtil.php b/src/Common/Util/DataTypeUtil.php new file mode 100644 index 0000000..a418e53 --- /dev/null +++ b/src/Common/Util/DataTypeUtil.php @@ -0,0 +1,51 @@ + $data + * @throws IncorrectDataTypeException + */ + public static function validateStringArray(array $data): void + { + foreach ($data as $item) { + if (!is_string($item)) { + throw new IncorrectDataTypeException(sprintf(self::INVALID_TYPE_ERR, 'string', gettype($item))); + } + } + } + + /** + * @param array $data + * @throws IncorrectDataTypeException + */ + public static function validateIntArray(array $data): void + { + foreach ($data as $item) { + if (!is_int($item)) { + throw new IncorrectDataTypeException(sprintf(self::INVALID_TYPE_ERR, 'integer', gettype($item))); + } + } + } + + /** + * @param array $data + * @throws IncorrectDataTypeException + */ + public static function validateFloatArray(array $data): void + { + foreach ($data as $item) { + if (!is_float($item)) { + throw new IncorrectDataTypeException(sprintf(self::INVALID_TYPE_ERR, 'float', gettype($item))); + } + } + } + +} diff --git a/src/Common/Util/InputDataUtil.php b/src/Common/Util/InputDataUtil.php new file mode 100644 index 0000000..4390ad0 --- /dev/null +++ b/src/Common/Util/InputDataUtil.php @@ -0,0 +1,82 @@ + $data + * @param string ...$keys + * @return DateTimeInterface|null + * @throws Exception + */ + public static function getNullableDate(array $data, string ...$keys): ?DateTimeInterface + { + $value = self::getNullableKey($data, ...$keys); + + return $value === null ? null : new DateTime($value); + } + + /** + * @param array $data + * @param string ...$keys + * @return string|null + */ + public static function getNullableString(array $data, string ...$keys): ?string + { + $value = self::getNullableKey($data, ...$keys); + + return $value === null ? null : (string) $value; + } + + /** + * @param array $data + * @param string ...$keys + * @return int|null + */ + public static function getNullableInt(array $data, string ...$keys): ?int + { + $value = self::getNullableKey($data, ...$keys); + + return $value === null ? null : (int) $value; + } + + /** + * @param array $data + * @param string ...$keys + * @return float|null + */ + public static function getNullableFloat(array $data, string ...$keys): ?float + { + $value = self::getNullableKey($data, ...$keys); + + return $value === null ? null : (float) $value; + } + + /** + * Returns content of nested item inside $data[$key1][$key2][$key3] or null if key does not exist + * @param array $data + * @param string ...$keys + * @return mixed|null + */ + private static function getNullableKey(array $data, string ...$keys) + { + foreach ($keys as $key) { + if (!isset($data[$key])) { + return null; + } + $data = $data[$key]; + } + + return $data; + } + +} diff --git a/src/Common/Util/JsonSerializeEntityTrait.php b/src/Common/Util/JsonSerializeEntityTrait.php new file mode 100644 index 0000000..9cc4d32 --- /dev/null +++ b/src/Common/Util/JsonSerializeEntityTrait.php @@ -0,0 +1,29 @@ + + */ + public function jsonSerialize(): array + { + /** @var array $data */ + $data = get_object_vars($this); + foreach ($data as $key => $value) { + if ($value instanceof JsonSerializable) { + $data[$key] = $value->jsonSerialize(); + } elseif ($value instanceof DateTimeInterface) { + $data[$key] = $value->format(InputDataUtil::DATE_TIME_FORMAT); + } + } + + return $data; + } + +} diff --git a/src/Exception/BadResponseException.php b/src/Exception/BadResponseException.php new file mode 100644 index 0000000..ad4939f --- /dev/null +++ b/src/Exception/BadResponseException.php @@ -0,0 +1,71 @@ +errorCodes = $errorCodes; + } + + /** + * @param string $message + * @param int $code + * @param GuzzleBadResponseException $previous + * @param array> $errorCodes + * @return static + */ + public static function createFromGuzzle(string $message, int $code, GuzzleBadResponseException $previous, array $errorCodes = []): BadResponseException + { + return new static( + $message, + $code, + $previous, + array_map(fn(array $item): ErrorCode => ErrorCode::createFromApi($item), $errorCodes), + ); + } + + /** + * @return ErrorCode[] + */ + public function getErrorCodes(): array + { + return $this->errorCodes; + } + + public function getRequest(): RequestInterface + { + /** @var GuzzleBadResponseException $previous */ + $previous = $this->getPrevious(); + + return $previous->getRequest(); + } + + public function getResponse(): ResponseInterface + { + /** @var GuzzleBadResponseException $previous */ + $previous = $this->getPrevious(); + + return $previous->getResponse(); + } + +} diff --git a/src/Exception/ErrorCode.php b/src/Exception/ErrorCode.php new file mode 100644 index 0000000..948f2e3 --- /dev/null +++ b/src/Exception/ErrorCode.php @@ -0,0 +1,60 @@ +message = $message; + $this->code = $code; + $this->attributes = $attributes; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['message'], + (string) $data['errorCode'], + $data['errorAttributes'], + ); + } + + public function getMessage(): string + { + return $this->message; + } + + public function getCode(): string + { + return $this->code; + } + + /** + * @return mixed[] + */ + public function getAttributes(): array + { + return $this->attributes; + } + +} diff --git a/src/Exception/ExceptionFactory.php b/src/Exception/ExceptionFactory.php new file mode 100644 index 0000000..2d187d2 --- /dev/null +++ b/src/Exception/ExceptionFactory.php @@ -0,0 +1,44 @@ +getMessage(), $e->getCode(), $e); + } + + public static function fromGuzzleBadResponse(GuzzleBadResponseException $e): MpApiException + { + if ($e->getResponse()->getBody()->isSeekable()) { + $e->getResponse()->getBody()->rewind(); + } + + $body = json_decode($e->getResponse()->getBody()->getContents(), true); + $message = $body['result']['message'] ?? $e->getMessage(); + $code = $e->getCode(); + + switch ($code) { + case 401: + return UnauthorizedException::createFromGuzzle($message, $code, $e); + case 404: + return NotFoundException::createFromGuzzle($message, $code, $e); + case 429: + return TooManyRequestsException::createFromGuzzle($message, $code, $e); + default: + /** @psalm-suppress PossiblyInvalidArgument - https://github.com/vimeo/psalm/issues/4295 */ + return BadResponseException::createFromGuzzle($message, $code, $e, $body['errorCodes'] ?? []); + } + } + +} diff --git a/src/Exception/IncorrectDataTypeException.php b/src/Exception/IncorrectDataTypeException.php new file mode 100644 index 0000000..4e55fbf --- /dev/null +++ b/src/Exception/IncorrectDataTypeException.php @@ -0,0 +1,8 @@ + + */ + private array $sortColumns = []; + private int $offset = 0; + private int $limit = 100; + + /** + * @return FilterItem[] + */ + public function getFilterItems(): array + { + return $this->filterItems; + } + + public function resetFilter(): void + { + $this->filterItems = []; + } + + /** + * Adds new FilterItem to filter, overwriting previous column filter if set + */ + public function addFilterItem(FilterItem $filterItem): void + { + $this->filterItems[$filterItem->getColumn()] = $filterItem; + } + + /** + * Removes FilterItem for column from filter if set + */ + public function removeFilterItem(string $column): void + { + if (isset($this->filterItems[$column])) { + unset($this->filterItems[$column]); + } + } + + /** + * @return array + */ + public function getSortColumns(): array + { + return $this->sortColumns; + } + + public function resetSort(): void + { + $this->sortColumns = []; + } + + public function prependSortColumn(string $column, string $direction = self::DIRECTION_ASC): void + { + if (isset($this->sortColumns[$column])) { + unset($this->sortColumns[$column]); + } + + $this->sortColumns = [$column => strtolower($direction)] + $this->sortColumns; + } + + public function addSortColumn(string $column, string $direction = self::DIRECTION_ASC): void + { + if (isset($this->sortColumns[$column])) { + unset($this->sortColumns[$column]); + } + + $this->sortColumns[$column] = strtolower($direction); + } + + /** + * Removes column from sort if set + */ + public function removeSortColumn(string $column): void + { + if (isset($this->sortColumns[$column])) { + unset($this->sortColumns[$column]); + } + } + + public function getOffset(): int + { + return $this->offset; + } + + public function setOffset(int $offset): void + { + $this->offset = $offset; + } + + public function getLimit(): int + { + return $this->limit; + } + + /** + * @throws InvalidArgumentException + */ + public function setLimit(int $limit): void + { + if ($limit <= 0) { + throw new InvalidArgumentException(sprintf('Filter limit must be a positive number above zero, [%d] provided', $limit)); + } + + $this->limit = $limit; + } + + /** + * @return array + */ + public function buildFilterQuery(): array + { + $query = []; + + foreach ($this->filterItems as $item) { + if ($item->getOperator()->equals(FilterOperatorEnum::EMPTY())) { + // column_name => value + $query[$item->getColumn()] = implode(',', $item->getValues()); + } else { + // column_name => nin:value1,value2,value3 + $query[$item->getColumn()] = sprintf(self::FILTER_QUERY_PATTERN, $item->getOperator()->getValue(), implode(',', $item->getValues())); + } + } + + $sort = []; + foreach ($this->sortColumns as $column => $direction) { + $sort[] = sprintf(self::SORT_QUERY_PATTERN, $column, $direction); + } + + if ($sort !== []) { + // _sort => column1:asc,column2:desc + $query['_sort'] = implode(',', $sort); + } + + $query['page'] = (int) (floor($this->getOffset() / $this->getLimit()) + 1); + $query['page_size'] = $this->getLimit(); + + // MPAPI for now uses paging instead of offset and limit + // $query['_offset'] = $this->getOffset(); + // $query['_limit'] = $this->getLimit(); + + return $query; + } + +} diff --git a/src/Filter/FilterItem.php b/src/Filter/FilterItem.php new file mode 100644 index 0000000..ed8a93f --- /dev/null +++ b/src/Filter/FilterItem.php @@ -0,0 +1,75 @@ +column = $column; + $this->values = $values; + $this->operator = $operator; + } + + /** + * @throws MpApiException + */ + public static function create(string $column, string $value, FilterOperatorEnum $operator): self + { + if ($operator->equalsOneOf(FilterOperatorEnum::BETWEEN(), FilterOperatorEnum::IN(), FilterOperatorEnum::NOT_IN())) { + throw new MpApiException(sprintf('Unsupported operator [%s] used. Please use provided create methods.', $operator->getValue())); + } + + return new self($column, [$value], $operator); + } + + public static function createInterval(string $column, string $value1, string $value2): self + { + return new self($column, [$value1, $value2], FilterOperatorEnum::BETWEEN()); + } + + public static function createInclusion(string $column, string ...$values): self + { + return new self($column, $values, FilterOperatorEnum::IN()); + } + + public static function createExclusion(string $column, string ...$values): self + { + return new self($column, $values, FilterOperatorEnum::NOT_IN()); + } + + public function getColumn(): string + { + return $this->column; + } + + /** + * @return string[] + */ + public function getValues(): array + { + return $this->values; + } + + public function getOperator(): FilterOperatorEnum + { + return $this->operator; + } + +} diff --git a/src/Filter/FilterOperatorEnum.php b/src/Filter/FilterOperatorEnum.php new file mode 100644 index 0000000..3a6899a --- /dev/null +++ b/src/Filter/FilterOperatorEnum.php @@ -0,0 +1,46 @@ +street = $street; + $this->city = $city; + $this->zip = $zip; + $this->country = $country; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['street'], + (string) $data['city'], + (string) $data['zip'], + (string) $data['country'], + ); + } + + public function getStreet(): string + { + return $this->street; + } + + public function getCity(): string + { + return $this->city; + } + + public function getZip(): string + { + return $this->zip; + } + + public function getCountry(): string + { + return $this->country; + } + +} diff --git a/src/Financial/Entity/Common/Attachment.php b/src/Financial/Entity/Common/Attachment.php new file mode 100644 index 0000000..bf9c2c9 --- /dev/null +++ b/src/Financial/Entity/Common/Attachment.php @@ -0,0 +1,45 @@ +filename = $filename; + $this->mime = $mime; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['filename'], + (string) $data['mime'], + ); + } + + public function getFilename(): string + { + return $this->filename; + } + + public function getMime(): string + { + return $this->mime; + } + +} diff --git a/src/Financial/Entity/Common/Customer.php b/src/Financial/Entity/Common/Customer.php new file mode 100644 index 0000000..3319536 --- /dev/null +++ b/src/Financial/Entity/Common/Customer.php @@ -0,0 +1,77 @@ +name = $name; + $this->registrationNumber = $registrationNumber; + $this->taxIdentification = $taxIdentification; + $this->vatNumber = $vatNumber; + $this->note = $note; + $this->address = $address; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['name'], + (string) $data['registrationNumber'], + (string) $data['taxIdentification'], + (string) $data['vatNumber'], + (string) $data['note'], + Address::createFromApi($data['address']), + ); + } + + public function getName(): string + { + return $this->name; + } + + public function getRegistrationNumber(): string + { + return $this->registrationNumber; + } + + public function getTaxIdentification(): string + { + return $this->taxIdentification; + } + + public function getVatNumber(): string + { + return $this->vatNumber; + } + + public function getNote(): string + { + return $this->note; + } + + public function getAddress(): Address + { + return $this->address; + } + +} diff --git a/src/Financial/Entity/Common/Supplier.php b/src/Financial/Entity/Common/Supplier.php new file mode 100644 index 0000000..7e928ff --- /dev/null +++ b/src/Financial/Entity/Common/Supplier.php @@ -0,0 +1,77 @@ +name = $name; + $this->registrationNumber = $registrationNumber; + $this->taxIdentification = $taxIdentification; + $this->vatNumber = $vatNumber; + $this->note = $note; + $this->address = $address; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['name'], + (string) $data['registrationNumber'], + (string) $data['taxIdentification'], + (string) $data['vatNumber'], + (string) $data['note'], + Address::createFromApi($data['address']), + ); + } + + public function getName(): string + { + return $this->name; + } + + public function getRegistrationNumber(): string + { + return $this->registrationNumber; + } + + public function getTaxIdentification(): string + { + return $this->taxIdentification; + } + + public function getVatNumber(): string + { + return $this->vatNumber; + } + + public function getNote(): string + { + return $this->note; + } + + public function getAddress(): Address + { + return $this->address; + } + +} diff --git a/src/Financial/Entity/Invoice/Bank.php b/src/Financial/Entity/Invoice/Bank.php new file mode 100644 index 0000000..c4dec17 --- /dev/null +++ b/src/Financial/Entity/Invoice/Bank.php @@ -0,0 +1,61 @@ +iban = $iban; + $this->swift = $swift; + $this->bankName = $bankName; + $this->bankAccount = $bankAccount; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['iban'], + (string) $data['swift'], + (string) $data['bankName'], + (string) $data['bankAccount'], + ); + } + + public function getIban(): string + { + return $this->iban; + } + + public function getSwift(): string + { + return $this->swift; + } + + public function getBankName(): string + { + return $this->bankName; + } + + public function getBankAccount(): string + { + return $this->bankAccount; + } + +} diff --git a/src/Financial/Entity/Invoice/Invoice.php b/src/Financial/Entity/Invoice/Invoice.php new file mode 100644 index 0000000..0cde16c --- /dev/null +++ b/src/Financial/Entity/Invoice/Invoice.php @@ -0,0 +1,205 @@ +invoiceNumber = $invoiceNumber; + $this->partner = $partner; + $this->createdAt = $createdAt; + $this->deliveryAt = $deliveryAt; + $this->dueDate = $dueDate; + $this->originDocumentId = $originDocumentId; + $this->currency = $currency; + $this->supplier = $supplier; + $this->customer = $customer; + $this->items = $items; + $this->filePath = $filePath; + $this->total = $total; + $this->taxRecap = $taxRecap; + $this->note = $note; + $this->purchNoC = $purchNoC; + $this->invoiceType = $invoiceType; + $this->invoiceIndicator = $invoiceIndicator; + $this->documentType = $documentType; + $this->invoiceTypeTag = $invoiceTypeTag; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['invoiceNumber'], + (string) $data['partner'], + InputDataUtil::getNullableDate($data, 'createdAt'), + InputDataUtil::getNullableDate($data, 'deliveryAt'), + InputDataUtil::getNullableDate($data, 'dueDate'), + InputDataUtil::getNullableString($data, 'originDocumentId'), + (string) $data['currency'], + Supplier::createFromApi($data['supplier']), + Customer::createFromApi($data['customer']), + ItemIterator::createFromApi($data['items']), + (string) $data['filePath'], + (float) $data['total'], + TaxRecap::createFromApi($data['taxRecap']), + (string) $data['note'], + (string) $data['purchNoC'], + (string) $data['invoiceType'], + (string) $data['invoiceIndicator'], + (string) $data['documentType'], + (string) $data['invoiceTypeTag'], + ); + } + + public function getInvoiceNumber(): int + { + return $this->invoiceNumber; + } + + public function getPartner(): string + { + return $this->partner; + } + + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + + public function getDeliveryAt(): ?DateTimeInterface + { + return $this->deliveryAt; + } + + public function getDueDate(): ?DateTimeInterface + { + return $this->dueDate; + } + + public function getOriginDocumentId(): ?string + { + return $this->originDocumentId; + } + + public function getCurrency(): string + { + return $this->currency; + } + + public function getSupplier(): Supplier + { + return $this->supplier; + } + + public function getCustomer(): Customer + { + return $this->customer; + } + + public function getItems(): ItemIterator + { + return $this->items; + } + + public function getFilePath(): string + { + return $this->filePath; + } + + public function getTotal(): float + { + return $this->total; + } + + public function getTaxRecap(): TaxRecap + { + return $this->taxRecap; + } + + public function getNote(): string + { + return $this->note; + } + + public function getPurchNoC(): string + { + return $this->purchNoC; + } + + public function getInvoiceType(): string + { + return $this->invoiceType; + } + + public function getInvoiceIndicator(): string + { + return $this->invoiceIndicator; + } + + public function getDocumentType(): string + { + return $this->documentType; + } + + public function getInvoiceTypeTag(): string + { + return $this->invoiceTypeTag; + } + +} diff --git a/src/Financial/Entity/Invoice/InvoiceList.php b/src/Financial/Entity/Invoice/InvoiceList.php new file mode 100644 index 0000000..ab4370a --- /dev/null +++ b/src/Financial/Entity/Invoice/InvoiceList.php @@ -0,0 +1,45 @@ + + * @property Invoice[] $data + */ +final class InvoiceList extends AbstractList +{ + + public static function createWithCallback(Closure $callback, Filter $filter): self + { + return new self($callback, $filter); + } + + /** + * @return false|Invoice + */ + public function current() + { + return current($this->data); + } + + /** + * @param array> $data + * @return Invoice[] + * @throws Exception + */ + protected function parseData(array $data): array + { + $items = []; + foreach ($data as $item) { + $items[] = Invoice::createFromApi($item); + } + + return $items; + } + +} diff --git a/src/Financial/Entity/Invoice/Item.php b/src/Financial/Entity/Invoice/Item.php new file mode 100644 index 0000000..a4a8be6 --- /dev/null +++ b/src/Financial/Entity/Invoice/Item.php @@ -0,0 +1,137 @@ +id = $id; + $this->articleId = $articleId; + $this->title = $title; + $this->titleEn = $titleEn; + $this->quantity = $quantity; + $this->unit = $unit; + $this->unitPrice = $unitPrice; + $this->vatPrice = $vatPrice; + $this->priceWithoutVat = $priceWithoutVat; + $this->vatRate = $vatRate; + $this->totalPrice = $totalPrice; + $this->orderId = $orderId; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['id'], + (int) $data['articleId'], + (string) $data['title'], + (string) $data['titleEn'], + (int) $data['quantity'], + (string) $data['unit'], + (float) $data['unitPrice'], + (float) $data['vatPrice'], + (float) $data['priceWithoutVat'], + (float) $data['vatRate'], + (float) $data['totalPrice'], + (int) $data['orderId'], + ); + } + + public function getId(): string + { + return $this->id; + } + + public function getArticleId(): int + { + return $this->articleId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getTitleEn(): string + { + return $this->titleEn; + } + + public function getQuantity(): int + { + return $this->quantity; + } + + public function getUnit(): string + { + return $this->unit; + } + + public function getUnitPrice(): float + { + return $this->unitPrice; + } + + public function getVatPrice(): float + { + return $this->vatPrice; + } + + public function getPriceWithoutVat(): float + { + return $this->priceWithoutVat; + } + + public function getVatRate(): float + { + return $this->vatRate; + } + + public function getTotalPrice(): float + { + return $this->totalPrice; + } + + public function getOrderId(): int + { + return $this->orderId; + } + +} diff --git a/src/Financial/Entity/Invoice/ItemIterator.php b/src/Financial/Entity/Invoice/ItemIterator.php new file mode 100644 index 0000000..0724ced --- /dev/null +++ b/src/Financial/Entity/Invoice/ItemIterator.php @@ -0,0 +1,46 @@ + + * @property array $data + */ +final class ItemIterator extends AbstractStringKeyIterator +{ + + private function __construct(Item ...$data) + { + foreach ($data as $invoiceItem) { + $this->data[$invoiceItem->getId()] = $invoiceItem; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Item => Item::createFromApi($item), $data) + ); + } + + /** + * @return false|Item + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Item + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Financial/Entity/Invoice/Supplier.php b/src/Financial/Entity/Invoice/Supplier.php new file mode 100644 index 0000000..d9c95ae --- /dev/null +++ b/src/Financial/Entity/Invoice/Supplier.php @@ -0,0 +1,45 @@ +bank = $bank; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['name'], + (string) $data['registrationNumber'], + (string) $data['taxIdentification'], + (string) $data['vatNumber'], + (string) $data['note'], + Address::createFromApi($data['address']), + Bank::createFromApi($data['bank']), + ); + } + + public function getBank(): Bank + { + return $this->bank; + } + +} diff --git a/src/Financial/Entity/Invoice/Tax.php b/src/Financial/Entity/Invoice/Tax.php new file mode 100644 index 0000000..95458b3 --- /dev/null +++ b/src/Financial/Entity/Invoice/Tax.php @@ -0,0 +1,61 @@ +tax = $tax; + $this->base = $base; + $this->total = $total; + $this->price = $price; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['tax'], + (float) $data['base'], + (float) $data['total'], + (float) $data['price'], + ); + } + + public function getTax(): string + { + return $this->tax; + } + + public function getBase(): float + { + return $this->base; + } + + public function getTotal(): float + { + return $this->total; + } + + public function getPrice(): float + { + return $this->price; + } + +} diff --git a/src/Financial/Entity/Invoice/TaxIterator.php b/src/Financial/Entity/Invoice/TaxIterator.php new file mode 100644 index 0000000..06ed6cd --- /dev/null +++ b/src/Financial/Entity/Invoice/TaxIterator.php @@ -0,0 +1,49 @@ + + * @property array $data + */ +final class TaxIterator extends AbstractStringKeyIterator +{ + + private function __construct(Tax ...$data) + { + foreach ($data as $tax) { + $this->data[$tax->getTax()] = $tax; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + $items = []; + foreach ($data as $values) { + $items[] = Tax::createFromApi($values); + } + + return new self(...$items); + } + + /** + * @return false|Tax + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Tax + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Financial/Entity/Invoice/TaxRecap.php b/src/Financial/Entity/Invoice/TaxRecap.php new file mode 100644 index 0000000..60f46fa --- /dev/null +++ b/src/Financial/Entity/Invoice/TaxRecap.php @@ -0,0 +1,45 @@ +total = $total; + $this->taxes = $taxes; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (float) $data['total'], + TaxIterator::createFromApi($data['taxes']), + ); + } + + public function getTotal(): float + { + return $this->total; + } + + public function getTaxes(): TaxIterator + { + return $this->taxes; + } + +} diff --git a/src/Financial/Entity/Offset/InvoiceSimple.php b/src/Financial/Entity/Offset/InvoiceSimple.php new file mode 100644 index 0000000..b8cc4ed --- /dev/null +++ b/src/Financial/Entity/Offset/InvoiceSimple.php @@ -0,0 +1,123 @@ +id = $id; + $this->created = $created; + $this->dueDate = $dueDate; + $this->sumPrice = $sumPrice; + $this->offsetPrice = $offsetPrice; + $this->remainPrice = $remainPrice; + $this->currency = $currency; + $this->purchNoC = $purchNoC; + $this->note = $note; + $this->invoiceNumber = $invoiceNumber; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['id'], + InputDataUtil::getNullableDate($data, 'created'), + InputDataUtil::getNullableDate($data, 'dueDate'), + (float) $data['sumPrice'], + (float) $data['offsetPrice'], + (float) $data['remainPrice'], + (string) $data['currency'], + (string) $data['purchNoC'], + (string) $data['note'], + (string) $data['invoiceNumber'], + ); + } + + public function getId(): int + { + return $this->id; + } + + public function getCreated(): ?DateTimeInterface + { + return $this->created; + } + + public function getDueDate(): ?DateTimeInterface + { + return $this->dueDate; + } + + public function getSumPrice(): float + { + return $this->sumPrice; + } + + public function getOffsetPrice(): float + { + return $this->offsetPrice; + } + + public function getRemainPrice(): float + { + return $this->remainPrice; + } + + public function getCurrency(): string + { + return $this->currency; + } + + public function getPurchNoC(): string + { + return $this->purchNoC; + } + + public function getNote(): string + { + return $this->note; + } + + public function getInvoiceNumber(): string + { + return $this->invoiceNumber; + } + +} diff --git a/src/Financial/Entity/Offset/InvoiceSimpleIterator.php b/src/Financial/Entity/Offset/InvoiceSimpleIterator.php new file mode 100644 index 0000000..f6eca3e --- /dev/null +++ b/src/Financial/Entity/Offset/InvoiceSimpleIterator.php @@ -0,0 +1,48 @@ + + * @property array $data + */ +final class InvoiceSimpleIterator extends AbstractIntKeyIterator +{ + + private function __construct(InvoiceSimple ...$data) + { + foreach ($data as $invoiceSimple) { + $this->data[$invoiceSimple->getId()] = $invoiceSimple; + } + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): InvoiceSimple => InvoiceSimple::createFromApi($item), $data) + ); + } + + /** + * @return false|InvoiceSimple + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?InvoiceSimple + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Financial/Entity/Offset/Offset.php b/src/Financial/Entity/Offset/Offset.php new file mode 100644 index 0000000..1bd8d4e --- /dev/null +++ b/src/Financial/Entity/Offset/Offset.php @@ -0,0 +1,144 @@ +partner = $partner; + $this->documentNumber = $documentNumber; + $this->createdAt = $createdAt; + $this->dueDate = $dueDate; + $this->currency = $currency; + $this->diffPrice = $diffPrice; + $this->variableSymbol = $variableSymbol; + $this->supplier = $supplier; + $this->customer = $customer; + $this->invoices = $invoices; + $this->orders = $orders; + $this->attachment = $attachment; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['partner'], + (string) $data['documentNumber'], + InputDataUtil::getNullableDate($data, 'createdAt'), + InputDataUtil::getNullableDate($data, 'dueDate'), + (string) $data['currency'], + (float) $data['diffPrice'], + (int) $data['variableSymbol'], + Supplier::createFromApi($data['supplier']), + Customer::createFromApi($data['customer']), + InvoiceSimpleIterator::createFromApi($data['invoices']), + OrderIterator::createFromApi($data['orders']), + Attachment::createFromApi($data['attachment']), + ); + } + + public function getPartner(): string + { + return $this->partner; + } + + public function getDocumentNumber(): string + { + return $this->documentNumber; + } + + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + + public function getDueDate(): ?DateTimeInterface + { + return $this->dueDate; + } + + public function getCurrency(): string + { + return $this->currency; + } + + public function getDiffPrice(): float + { + return $this->diffPrice; + } + + public function getVariableSymbol(): int + { + return $this->variableSymbol; + } + + public function getSupplier(): Supplier + { + return $this->supplier; + } + + public function getCustomer(): Customer + { + return $this->customer; + } + + public function getInvoices(): InvoiceSimpleIterator + { + return $this->invoices; + } + + public function getOrders(): OrderIterator + { + return $this->orders; + } + + public function getAttachment(): Attachment + { + return $this->attachment; + } + +} diff --git a/src/Financial/Entity/Offset/OffsetList.php b/src/Financial/Entity/Offset/OffsetList.php new file mode 100644 index 0000000..d756e20 --- /dev/null +++ b/src/Financial/Entity/Offset/OffsetList.php @@ -0,0 +1,45 @@ + + * @property Offset[] $data + */ +final class OffsetList extends AbstractList +{ + + public static function createWithCallback(Closure $callback, Filter $filter): self + { + return new self($callback, $filter); + } + + /** + * @return false|Offset + */ + public function current() + { + return current($this->data); + } + + /** + * @param array> $data + * @return Offset[] + * @throws Exception + */ + protected function parseData(array $data): array + { + $items = []; + foreach ($data as $item) { + $items[] = Offset::createFromApi($item); + } + + return $items; + } + +} diff --git a/src/Financial/Entity/Offset/Order.php b/src/Financial/Entity/Offset/Order.php new file mode 100644 index 0000000..4ffb911 --- /dev/null +++ b/src/Financial/Entity/Offset/Order.php @@ -0,0 +1,96 @@ +id = $id; + $this->created = $created; + $this->dueDate = $dueDate; + $this->sumPrice = $sumPrice; + $this->offsetPrice = $offsetPrice; + $this->remainPrice = $remainPrice; + $this->currency = $currency; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['id'], + InputDataUtil::getNullableDate($data, 'created'), + InputDataUtil::getNullableDate($data, 'dueDate'), + (float) $data['sumPrice'], + (float) $data['offsetPrice'], + (float) $data['remainPrice'], + (string) $data['currency'], + ); + } + + public function getId(): int + { + return $this->id; + } + + public function getCreated(): ?DateTimeInterface + { + return $this->created; + } + + public function getDueDate(): ?DateTimeInterface + { + return $this->dueDate; + } + + public function getSumPrice(): float + { + return $this->sumPrice; + } + + public function getOffsetPrice(): float + { + return $this->offsetPrice; + } + + public function getRemainPrice(): float + { + return $this->remainPrice; + } + + public function getCurrency(): string + { + return $this->currency; + } + +} diff --git a/src/Financial/Entity/Offset/OrderIterator.php b/src/Financial/Entity/Offset/OrderIterator.php new file mode 100644 index 0000000..23ebdf1 --- /dev/null +++ b/src/Financial/Entity/Offset/OrderIterator.php @@ -0,0 +1,48 @@ + + * @property array $data + */ +final class OrderIterator extends AbstractIntKeyIterator +{ + + private function __construct(Order ...$data) + { + foreach ($data as $order) { + $this->data[$order->getId()] = $order; + } + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Order => Order::createFromApi($item), $data) + ); + } + + /** + * @return false|Order + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?Order + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Financial/FinancialClient.php b/src/Financial/FinancialClient.php new file mode 100644 index 0000000..0803f1b --- /dev/null +++ b/src/Financial/FinancialClient.php @@ -0,0 +1,75 @@ + $this->sendQueryRequest(self::INVOICE_LIST, $filter->buildFilterQuery()) + ), + $filter ?? new Filter(), + ); + } + + public function getInvoice(string $invoiceId): Invoice + { + return Invoice::createFromApi( + $this->sendJson('GET', sprintf(self::INVOICE_DETAIL, $invoiceId))['data'] + ); + } + + public function downloadInvoice(string $invoiceId): ResponseInterface + { + return $this->send( + new Request('GET', sprintf(self::INVOICE_DOWNLOAD, $invoiceId), $this->getAppHeaders()) + ); + } + + public function listOffsets(?Filter $filter): OffsetList + { + return OffsetList::createWithCallback( + Closure::fromCallable( + fn(Filter $filter): array => $this->sendQueryRequest(self::OFFSET_LIST, $filter->buildFilterQuery()) + ), + $filter ?? new Filter(), + ); + } + + public function getOffset(string $offsetId): Offset + { + return Offset::createFromApi( + $this->sendJson('GET', sprintf(self::OFFSET_DETAIL, $offsetId))['data'] + ); + } + + public function downloadOffset(string $offsetId): ResponseInterface + { + return $this->send( + new Request('GET', sprintf(self::OFFSET_DOWNLOAD, $offsetId), $this->getAppHeaders()) + ); + } + +} diff --git a/src/Label/Entity/Label.php b/src/Label/Entity/Label.php new file mode 100644 index 0000000..f9431cb --- /dev/null +++ b/src/Label/Entity/Label.php @@ -0,0 +1,45 @@ +id = $id; + $this->title = $title; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) $data['id'], + (string) $data['title'], + ); + } + + public function getId(): string + { + return $this->id; + } + + public function getTitle(): string + { + return $this->title; + } + +} diff --git a/src/Label/Entity/LabelIterator.php b/src/Label/Entity/LabelIterator.php new file mode 100644 index 0000000..44e45e6 --- /dev/null +++ b/src/Label/Entity/LabelIterator.php @@ -0,0 +1,46 @@ + + * @property array $data + */ +final class LabelIterator extends AbstractStringKeyIterator +{ + + private function __construct(Label ...$data) + { + foreach ($data as $label) { + $this->data[$label->getId()] = $label; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Label => Label::createFromApi($item), $data) + ); + } + + /** + * @return false|Label + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Label + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Label/LabelClient.php b/src/Label/LabelClient.php new file mode 100644 index 0000000..44cbb9a --- /dev/null +++ b/src/Label/LabelClient.php @@ -0,0 +1,19 @@ +sendJson('GET', self::LIST)['data']); + } + +} diff --git a/src/MpApiClient.php b/src/MpApiClient.php new file mode 100644 index 0000000..b889565 --- /dev/null +++ b/src/MpApiClient.php @@ -0,0 +1,107 @@ +brandClient = new BrandClient($client, $appTag); + $this->categoryClient = new CategoryClient($client, $appTag); + $this->checksClient = new ChecksClient($client, $appTag); + $this->financialClient = new FinancialClient($client, $appTag); + $this->labelClient = new LabelClient($client, $appTag); + $this->ordersClient = new OrderClient($client, $appTag); + $this->articleClient = new ArticleClient($client, $appTag); + $this->shopClient = new ShopClient($client, $appTag); + $this->supplyDelayClient = new SupplyDelayClient($client, $appTag); + } + + public static function createFromOptions(string $appTag, MpApiClientOptions $options): self + { + return new self(new GuzzleClient($options->getGuzzleOptionsArray()), $appTag); + } + + public function orders(): OrderClientInterface + { + return $this->ordersClient; + } + + public function article(): ArticleClientInterface + { + return $this->articleClient; + } + + public function financial(): FinancialClientInterface + { + return $this->financialClient; + } + + public function brand(): BrandClientInterface + { + return $this->brandClient; + } + + public function category(): CategoryClientInterface + { + return $this->categoryClient; + } + + public function checks(): ChecksClientInterface + { + return $this->checksClient; + } + + public function shop(): ShopClientInterface + { + return $this->shopClient; + } + + public function label(): LabelClientInterface + { + return $this->labelClient; + } + + public function supplyDelay(): SupplyDelayClientInterface + { + return $this->supplyDelayClient; + } + +} diff --git a/src/MpApiClientOptions.php b/src/MpApiClientOptions.php new file mode 100644 index 0000000..adde85d --- /dev/null +++ b/src/MpApiClientOptions.php @@ -0,0 +1,91 @@ +authMiddleware = $authMiddleware; + + $handler = new CurlHandler(); + $stack = HandlerStack::create($handler); + $stack->push($authMiddleware->getHandler()); + $this->handlerStack = $stack; + } + + public function getAuthMiddleware(): AuthMiddlewareInterface + { + return $this->authMiddleware; + } + + public function setAuthMiddleware(AuthMiddlewareInterface $authMiddleware): void + { + $this->authMiddleware = $authMiddleware; + } + + public function getBaseUri(): string + { + return $this->baseUri; + } + + public function setBaseUri(string $baseUri): void + { + $this->baseUri = $baseUri; + } + + public function getTimeout(): int + { + return $this->timeout; + } + + public function setTimeout(int $timeout): void + { + $this->timeout = $timeout; + } + + public function getAllowRedirects(): bool + { + return $this->allowRedirects; + } + + public function setAllowRedirects(bool $allowRedirects): void + { + $this->allowRedirects = $allowRedirects; + } + + public function getHandlerStack(): HandlerStack + { + return $this->handlerStack; + } + + public function setHandlerStack(HandlerStack $handlerStack): void + { + $this->handlerStack = $handlerStack; + } + + /** + * @return array + */ + public function getGuzzleOptionsArray(): array + { + return [ + 'base_uri' => $this->getBaseUri(), + 'timeout' => $this->getTimeout(), + 'allow_redirects' => $this->getAllowRedirects(), + 'handler' => $this->getHandlerStack(), + ]; + } + +} diff --git a/src/Order/DTO/ShippingLabelRequest.php b/src/Order/DTO/ShippingLabelRequest.php new file mode 100644 index 0000000..4c14c86 --- /dev/null +++ b/src/Order/DTO/ShippingLabelRequest.php @@ -0,0 +1,79 @@ + + */ + private array $labels = []; + + public function __construct(string $labelType, int $firstPosition, int $labelsPerPage) + { + $this->labelType = $labelType; + $this->firstPosition = $firstPosition; + $this->labelsPerPage = $labelsPerPage; + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + $labels = []; + foreach ($this->getLabels() as $orderId => $parcelCount) { + $labels[] = ['order_id' => $orderId, 'parcel_count' => $parcelCount]; + } + + return [ + 'labels_type' => $this->getLabelType(), + 'first_position' => $this->getFirstPosition(), + 'labels_per_page' => $this->getLabelsPerPage(), + 'labels' => $labels, + ]; + } + + public function addLabel(int $orderId, int $parcelCount): void + { + $this->labels[$orderId] = $parcelCount; + } + + public function removeLabel(int $orderId): void + { + if (isset($this->labels[$orderId])) { + unset($this->labels[$orderId]); + } + } + + public function getLabelType(): string + { + return $this->labelType; + } + + public function getFirstPosition(): int + { + return $this->firstPosition; + } + + public function getLabelsPerPage(): int + { + return $this->labelsPerPage; + } + + /** + * @return array + */ + public function getLabels(): array + { + return $this->labels; + } + +} diff --git a/src/Order/DTO/StatusRequest.php b/src/Order/DTO/StatusRequest.php new file mode 100644 index 0000000..8721643 --- /dev/null +++ b/src/Order/DTO/StatusRequest.php @@ -0,0 +1,103 @@ +status = $status; + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + $out = [ + 'status' => $this->getStatus()->getValue(), + ]; + + if ($this->confirmed !== null) { + $out['confirmed'] = $this->confirmed; + } + + if ($this->trackingUrl !== null) { + $out['tracking_url'] = $this->trackingUrl; + } + + if ($this->trackingNumber !== null) { + $out['tracking_number'] = $this->trackingNumber; + } + + if ($this->deliveredAt !== null) { + $out['delivered_at'] = $this->deliveredAt->format(InputDataUtil::DATE_TIME_FORMAT); + } + + if ($this->firstDeliveryAttempt !== null) { + $out['first_delivery_attempt'] = $this->firstDeliveryAttempt->format(InputDataUtil::DATE_TIME_FORMAT); + } + + return $out; + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function getConfirmed(): ?bool + { + return $this->confirmed; + } + + public function setConfirmed(bool $confirmed): void + { + $this->confirmed = $confirmed; + } + + public function getTrackingNumber(): ?string + { + return $this->trackingNumber; + } + + public function getTrackingUrl(): ?string + { + return $this->trackingUrl; + } + + public function getDeliveredAt(): ?DateTimeInterface + { + return $this->deliveredAt; + } + + public function getFirstDeliveryAttempt(): ?DateTimeInterface + { + return $this->firstDeliveryAttempt; + } + + public function setTracking(string $trackingNumber, string $trackingUrl): void + { + $this->trackingNumber = $trackingNumber; + $this->trackingUrl = $trackingUrl; + } + + public function setDelivery(DateTimeInterface $deliveredAt, DateTimeInterface $firstDeliveryAttempt): void + { + $this->deliveredAt = $deliveredAt; + $this->firstDeliveryAttempt = $firstDeliveryAttempt; + } + +} diff --git a/src/Order/Entity/BasicOrder.php b/src/Order/Entity/BasicOrder.php new file mode 100644 index 0000000..e6b7662 --- /dev/null +++ b/src/Order/Entity/BasicOrder.php @@ -0,0 +1,159 @@ +id = $id; + $this->purchaseId = $purchaseId; + $this->customerId = $customerId; + $this->customer = $customer; + $this->cod = $cod; + $this->paymentType = $paymentType; + $this->shipDate = $shipDate; + $this->trackingNumber = $trackingNumber; + $this->trackingUrl = $trackingUrl; + $this->deliveredAt = $deliveredAt; + $this->status = $status; + $this->confirmed = $confirmed; + $this->test = $test; + $this->mdp = $mdp; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['id'], + (int) $data['purchase_id'], + (int) $data['customer_id'], + (string) $data['customer'], + (float) $data['cod'], + (string) $data['payment_type'], + InputDataUtil::getNullableDate($data, 'ship_date'), + InputDataUtil::getNullableString($data, 'tracking_number'), + InputDataUtil::getNullableString($data, 'tracking_url'), + InputDataUtil::getNullableDate($data, 'delivered_at'), + new StatusEnum((string) $data[StatusEnum::KEY_NAME]), + (bool) $data['confirmed'], + (bool) $data['test'], + (bool) $data['mdp'], + ); + } + + public function getId(): int + { + return $this->id; + } + + public function getPurchaseId(): int + { + return $this->purchaseId; + } + + public function getCustomerId(): int + { + return $this->customerId; + } + + public function getCustomer(): string + { + return $this->customer; + } + + public function getCod(): float + { + return $this->cod; + } + + public function getPaymentType(): string + { + return $this->paymentType; + } + + public function getShipDate(): ?DateTimeInterface + { + return $this->shipDate; + } + + public function getTrackingNumber(): ?string + { + return $this->trackingNumber; + } + + public function getTrackingUrl(): ?string + { + return $this->trackingUrl; + } + + public function getDeliveredAt(): ?DateTimeInterface + { + return $this->deliveredAt; + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function isConfirmed(): bool + { + return $this->confirmed; + } + + public function isTest(): bool + { + return $this->test; + } + + public function isMdp(): bool + { + return $this->mdp; + } + +} diff --git a/src/Order/Entity/BasicOrderList.php b/src/Order/Entity/BasicOrderList.php new file mode 100644 index 0000000..5a0ea6a --- /dev/null +++ b/src/Order/Entity/BasicOrderList.php @@ -0,0 +1,48 @@ + + * @property BasicOrder[] $data + */ +final class BasicOrderList extends AbstractList +{ + + /** + * @throws Exception + */ + public static function createWithCallback(Closure $callback, Filter $filter): self + { + return new self($callback, $filter); + } + + /** + * @return false|BasicOrder + */ + public function current() + { + return current($this->data); + } + + /** + * @param array> $data + * @return BasicOrder[] + * @throws Exception + */ + protected function parseData(array $data): array + { + $items = []; + foreach ($data as $item) { + $items[] = BasicOrder::createFromApi($item); + } + + return $items; + } + +} diff --git a/src/Order/Entity/Branches.php b/src/Order/Entity/Branches.php new file mode 100644 index 0000000..e1bf1a6 --- /dev/null +++ b/src/Order/Entity/Branches.php @@ -0,0 +1,61 @@ +branchId = $branchId; + $this->secondaryBranchId = $secondaryBranchId; + $this->lastChange = $time; + } + + /** + * @param array $data + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + InputDataUtil::getNullableInt($data, 'branch_id'), + InputDataUtil::getNullableInt($data, 'secondary_branch_id'), + InputDataUtil::getNullableDate($data, 'last_change'), + ); + } + + public function getBranchId(): ?int + { + return $this->branchId; + } + + public function getSecondaryBranchId(): ?int + { + return $this->secondaryBranchId; + } + + public function getLastChange(): ?DateTimeInterface + { + return $this->lastChange; + } + + public function isOverridden(): bool + { + return $this->getSecondaryBranchId() !== null; + } + +} diff --git a/src/Order/Entity/ConsignmentStatus.php b/src/Order/Entity/ConsignmentStatus.php new file mode 100644 index 0000000..cf49fb7 --- /dev/null +++ b/src/Order/Entity/ConsignmentStatus.php @@ -0,0 +1,57 @@ +id = $id; + $this->name = $name; + $this->date = $date; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['id'], + (string) $data['name'], + new DateTime($data['date']), + ); + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function getDate(): DateTimeInterface + { + return $this->date; + } + +} diff --git a/src/Order/Entity/ConsignmentStatusIterator.php b/src/Order/Entity/ConsignmentStatusIterator.php new file mode 100644 index 0000000..e66736a --- /dev/null +++ b/src/Order/Entity/ConsignmentStatusIterator.php @@ -0,0 +1,46 @@ + + * @property ConsignmentStatus[] $data + */ +final class ConsignmentStatusIterator extends AbstractIntKeyIterator +{ + + private function __construct(ConsignmentStatus ...$data) + { + $this->data = $data; + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): ConsignmentStatus => ConsignmentStatus::createFromApi($item), $data) + ); + } + + /** + * @return false|ConsignmentStatus + */ + public function current() + { + return current($this->data); + } + + public function get(int $key): ?ConsignmentStatus + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Order/Entity/Customer.php b/src/Order/Entity/Customer.php new file mode 100644 index 0000000..eb6194c --- /dev/null +++ b/src/Order/Entity/Customer.php @@ -0,0 +1,111 @@ +customerId = $customerId; + $this->name = $name; + $this->company = $company; + $this->phone = $phone; + $this->email = $email; + $this->street = $street; + $this->city = $city; + $this->zip = $zip; + $this->country = $country; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['customer_id'], + (string) $data['name'], + InputDataUtil::getNullableString($data, 'company'), + (string) $data['phone'], + (string) $data['email'], + (string) $data['street'], + (string) $data['city'], + (string) $data['zip'], + (string) $data['country'], + ); + } + + public function getCustomerId(): int + { + return $this->customerId; + } + + public function getName(): string + { + return $this->name; + } + + public function getCompany(): ?string + { + return $this->company; + } + + public function getPhone(): string + { + return $this->phone; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getStreet(): string + { + return $this->street; + } + + public function getCity(): string + { + return $this->city; + } + + public function getZip(): string + { + return $this->zip; + } + + public function getCountry(): string + { + return $this->country; + } + +} diff --git a/src/Order/Entity/Item.php b/src/Order/Entity/Item.php new file mode 100644 index 0000000..96433d0 --- /dev/null +++ b/src/Order/Entity/Item.php @@ -0,0 +1,110 @@ +id = $id; + $this->articleId = $articleId; + $this->quantity = $quantity; + $this->price = $price; + $this->vat = $vat; + $this->commission = $commission; + $this->title = $title; + $this->serialNumbers = $serialNumbers; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + $data['id'], + (int) $data['article_id'], + (int) $data['quantity'], + (float) $data['price'], + (int) $data['vat'], + InputDataUtil::getNullableFloat($data, 'commission'), + (string) $data['title'], + $data['serial_numbers'], + ); + } + + public function getId(): string + { + return $this->id; + } + + public function getArticleId(): int + { + return $this->articleId; + } + + public function getQuantity(): int + { + return $this->quantity; + } + + public function getPrice(): float + { + return $this->price; + } + + public function getVat(): int + { + return $this->vat; + } + + public function getCommission(): ?float + { + return $this->commission; + } + + public function getTitle(): string + { + return $this->title; + } + + /** + * @return string[] + */ + public function getSerialNumbers(): array + { + return $this->serialNumbers; + } + +} diff --git a/src/Order/Entity/ItemIterator.php b/src/Order/Entity/ItemIterator.php new file mode 100644 index 0000000..d738858 --- /dev/null +++ b/src/Order/Entity/ItemIterator.php @@ -0,0 +1,48 @@ + + * @property array $data + */ +final class ItemIterator extends AbstractStringKeyIterator +{ + + private function __construct(Item ...$data) + { + foreach ($data as $orderItem) { + $this->data[$orderItem->getId()] = $orderItem; + } + } + + /** + * @param array> $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_map(fn(array $item): Item => Item::createFromApi($item), $data) + ); + } + + /** + * @return false|Item + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Item + { + return $this->data[$key] ?? null; + } + +} diff --git a/src/Order/Entity/Order.php b/src/Order/Entity/Order.php new file mode 100644 index 0000000..8be2183 --- /dev/null +++ b/src/Order/Entity/Order.php @@ -0,0 +1,339 @@ +id = $id; + $this->purchaseId = $purchaseId; + $this->currency = $currency; + $this->deliveryPrice = $deliveryPrice; + $this->codPrice = $codPrice; + $this->cod = $cod; + $this->discount = $discount; + $this->paymentType = $paymentType; + $this->deliveryMethod = $deliveryMethod; + $this->deliveryMethodId = $deliveryMethodId; + $this->branchId = $branchId; + $this->branches = $branches; + $this->trackingNumber = $trackingNumber; + $this->trackingUrl = $trackingUrl; + $this->shipDate = $shipDate; + $this->deliveryDate = $deliveryDate; + $this->deliveredAt = $deliveredAt; + $this->firstDeliveryAttempt = $firstDeliveryAttempt; + $this->customer = $address; + $this->confirmed = $confirmed; + $this->status = $status; + $this->items = $items; + $this->test = $test; + $this->mdp = $mdp; + $this->readyToReturn = $readyToReturn; + $this->shipped = $shipped; + $this->open = $open; + $this->blocked = $blocked; + $this->lost = $lost; + $this->returned = $returned; + $this->cancelled = $cancelled; + $this->delivered = $delivered; + $this->shipping = $shipping; + $this->ulozenkaStatusHistory = $ulozenkaStatusHistory; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['id'], + (int) $data['purchase_id'], + (string) $data['currency'], + (float) $data['delivery_price'], + (float) $data['cod_price'], + (float) $data['cod'], + (float) $data['discount'], + (string) $data['payment_type'], + (string) $data['delivery_method'], + (string) $data['delivery_method_id'], + InputDataUtil::getNullableInt($data, 'branch_id'), + Branches::createFromApi($data['branches']), + InputDataUtil::getNullableString($data, 'tracking_number'), + InputDataUtil::getNullableString($data, 'tracking_url'), + InputDataUtil::getNullableDate($data, 'ship_date'), + InputDataUtil::getNullableDate($data, 'delivery_date'), + InputDataUtil::getNullableDate($data, 'delivered_at'), + InputDataUtil::getNullableDate($data, 'first_delivery_attempt'), + Customer::createFromApi($data['address']), + (bool) $data['confirmed'], + new StatusEnum((string) $data[StatusEnum::KEY_NAME]), + ItemIterator::createFromApi($data['items']), + (bool) $data['test'], + (bool) $data['mdp'], + (bool) $data['ready_to_return'], + InputDataUtil::getNullableDate($data, 'shipped'), + InputDataUtil::getNullableDate($data, 'open'), + InputDataUtil::getNullableDate($data, 'blocked'), + InputDataUtil::getNullableDate($data, 'lost'), + InputDataUtil::getNullableDate($data, 'returned'), + InputDataUtil::getNullableDate($data, 'cancelled'), + InputDataUtil::getNullableDate($data, 'delivered'), + InputDataUtil::getNullableDate($data, 'shipping'), + ConsignmentStatusIterator::createFromApi($data['ulozenka_status_history']), + ); + } + + public function getId(): int + { + return $this->id; + } + + public function getPurchaseId(): int + { + return $this->purchaseId; + } + + public function getCurrency(): string + { + return $this->currency; + } + + public function getDeliveryPrice(): float + { + return $this->deliveryPrice; + } + + public function getCodPrice(): float + { + return $this->codPrice; + } + + public function getCod(): float + { + return $this->cod; + } + + public function getDiscount(): float + { + return $this->discount; + } + + public function getPaymentType(): string + { + return $this->paymentType; + } + + public function getDeliveryMethod(): string + { + return $this->deliveryMethod; + } + + public function getDeliveryMethodId(): string + { + return $this->deliveryMethodId; + } + + public function getBranchId(): ?int + { + return $this->branchId; + } + + public function getBranches(): Branches + { + return $this->branches; + } + + public function getTrackingNumber(): ?string + { + return $this->trackingNumber; + } + + public function getTrackingUrl(): ?string + { + return $this->trackingUrl; + } + + public function getShipDate(): ?DateTimeInterface + { + return $this->shipDate; + } + + public function getDeliveryDate(): ?DateTimeInterface + { + return $this->deliveryDate; + } + + public function getDeliveredAt(): ?DateTimeInterface + { + return $this->deliveredAt; + } + + public function getFirstDeliveryAttempt(): ?DateTimeInterface + { + return $this->firstDeliveryAttempt; + } + + public function getCustomer(): Customer + { + return $this->customer; + } + + public function isConfirmed(): bool + { + return $this->confirmed; + } + + public function getStatus(): StatusEnum + { + return $this->status; + } + + public function getItems(): ItemIterator + { + return $this->items; + } + + public function isTest(): bool + { + return $this->test; + } + + public function isMdp(): bool + { + return $this->mdp; + } + + public function isReadyToReturn(): bool + { + return $this->readyToReturn; + } + + public function getShipped(): ?DateTimeInterface + { + return $this->shipped; + } + + public function getOpen(): ?DateTimeInterface + { + return $this->open; + } + + public function getBlocked(): ?DateTimeInterface + { + return $this->blocked; + } + + public function getLost(): ?DateTimeInterface + { + return $this->lost; + } + + public function getReturned(): ?DateTimeInterface + { + return $this->returned; + } + + public function getCancelled(): ?DateTimeInterface + { + return $this->cancelled; + } + + public function getDelivered(): ?DateTimeInterface + { + return $this->delivered; + } + + public function getShipping(): ?DateTimeInterface + { + return $this->shipping; + } + + public function getUlozenkaStatusHistory(): ConsignmentStatusIterator + { + return $this->ulozenkaStatusHistory; + } + +} diff --git a/src/Order/Entity/Stats.php b/src/Order/Entity/Stats.php new file mode 100644 index 0000000..fc1347a --- /dev/null +++ b/src/Order/Entity/Stats.php @@ -0,0 +1,93 @@ +blocked = $blocked; + $this->open = $open; + $this->shipping = $shipping; + $this->shipped = $shipped; + $this->cancelled = $cancelled; + $this->delivered = $delivered; + $this->lost = $lost; + $this->returned = $returned; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (int) $data['blocked'], + (int) $data['open'], + (int) $data['shipping'], + (int) $data['shipped'], + (int) $data['cancelled'], + (int) $data['delivered'], + (int) $data['lost'], + (int) $data['returned'], + ); + } + + public function getBlocked(): int + { + return $this->blocked; + } + + public function getOpen(): int + { + return $this->open; + } + + public function getShipping(): int + { + return $this->shipping; + } + + public function getShipped(): int + { + return $this->shipped; + } + + public function getCancelled(): int + { + return $this->cancelled; + } + + public function getDelivered(): int + { + return $this->delivered; + } + + public function getLost(): int + { + return $this->lost; + } + + public function getReturned(): int + { + return $this->returned; + } + +} diff --git a/src/Order/Entity/StatusEnum.php b/src/Order/Entity/StatusEnum.php new file mode 100644 index 0000000..6386e99 --- /dev/null +++ b/src/Order/Entity/StatusEnum.php @@ -0,0 +1,42 @@ +trackingNumber = $trackingNumber; + $this->trackingUrl = $trackingUrl; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + (string) ($data['tracking_number'] ?? ''), + (string) ($data['tracking_url'] ?? ''), + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'tracking_number' => $this->getTrackingNumber(), + 'tracking_url' => $this->getTrackingUrl(), + ]; + } + + public function getTrackingNumber(): string + { + return $this->trackingNumber; + } + + public function getTrackingUrl(): string + { + return $this->trackingUrl; + } + +} diff --git a/src/Order/OrderClient.php b/src/Order/OrderClient.php new file mode 100644 index 0000000..a6e398c --- /dev/null +++ b/src/Order/OrderClient.php @@ -0,0 +1,83 @@ +addFilterItem(FilterItem::create('filter', 'basic', FilterOperatorEnum::EMPTY())); + + return BasicOrderList::createWithCallback( + Closure::fromCallable( + fn(Filter $filter) => $this->sendQueryRequest(self::LIST, $filter->buildFilterQuery()) + ), + $filter, + ); + } + + public function get(int $orderId): Order + { + return Order::createFromApi( + $this->sendJson('GET', sprintf(self::DETAIL, $orderId))['data'] + ); + } + + public function stats(int $days = 30): Stats + { + return Stats::createFromApi( + $this->sendQueryRequest(self::STATS, ['days' => $days])['data'] + ); + } + + public function confirmOrder(int $orderId): void + { + $this->sendJson('POST', sprintf(self::DETAIL, $orderId), ['confirmed' => true]); + } + + public function setStatus(int $orderId, StatusRequest $request): void + { + $this->sendJson('PUT', sprintf(self::DETAIL, $orderId), $request->getArrayForApi()); + } + + public function setTracking(int $orderId, Tracking $tracking): void + { + $this->sendJson('PUT', sprintf(self::TRACKING, $orderId), $tracking->getArrayForApi()); + } + + public function setItemSerialNumbers(int $orderId, int $itemId, string ...$serialNumbers): void + { + $this->sendJson('PUT', sprintf(self::ITEM_SERIAL_NUMBERS, $orderId, $itemId), $serialNumbers); + } + + public function createShippingLabels(ShippingLabelRequest $labelRequest): string + { + return $this->sendJson('PUT', self::LABELS, $labelRequest->getArrayForApi())['data']['labels_raw']; + } + +} diff --git a/src/Shop/Entity/Shop.php b/src/Shop/Entity/Shop.php new file mode 100644 index 0000000..3bbee46 --- /dev/null +++ b/src/Shop/Entity/Shop.php @@ -0,0 +1,77 @@ +shopId = $shopId; + $this->countryId = $countryId; + $this->name = $name; + $this->currencyIso = $currencyIso; + $this->currencySymbol = $currencySymbol; + $this->url = $url; + } + + /** + * @param array $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + new ShopIdEnum((string) $data['shop_id']), + (string) $data['country_id'], + (string) $data['name'], + (string) $data['currency_iso'], + (string) $data['currency_symbol'], + (string) $data['url'], + ); + } + + public function getShopId(): ShopIdEnum + { + return $this->shopId; + } + + public function getCountryId(): string + { + return $this->countryId; + } + + public function getName(): string + { + return $this->name; + } + + public function getCurrencyIso(): string + { + return $this->currencyIso; + } + + public function getCurrencySymbol(): string + { + return $this->currencySymbol; + } + + public function getUrl(): string + { + return $this->url; + } + +} diff --git a/src/Shop/Entity/ShopIdEnum.php b/src/Shop/Entity/ShopIdEnum.php new file mode 100644 index 0000000..75f3faf --- /dev/null +++ b/src/Shop/Entity/ShopIdEnum.php @@ -0,0 +1,39 @@ + + * @property array $data + */ +final class ShopIterator extends AbstractStringKeyIterator +{ + + private function __construct(Shop ...$data) + { + foreach ($data as $shop) { + $this->data[$shop->getShopId()->getValue()] = $shop; + } + } + + /** + * @param array> $data + * + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + ...array_values(array_map(fn(array $item): Shop => Shop::createFromApi($item), $data)) + ); + } + + /** + * @return false|Shop + */ + public function current() + { + return current($this->data); + } + + public function get(string $key): ?Shop + { + return $this->data[$key] ?? null; + } + + public function getByShopId(ShopIdEnum $shopId): ?Shop + { + return $this->get($shopId->getValue()); + } + +} diff --git a/src/Shop/ShopClient.php b/src/Shop/ShopClient.php new file mode 100644 index 0000000..d215f04 --- /dev/null +++ b/src/Shop/ShopClient.php @@ -0,0 +1,19 @@ +sendJson('GET', self::LIST)['data']); + } + +} diff --git a/src/SupplyDelay/Entity/SupplyDelay.php b/src/SupplyDelay/Entity/SupplyDelay.php new file mode 100644 index 0000000..b8f473f --- /dev/null +++ b/src/SupplyDelay/Entity/SupplyDelay.php @@ -0,0 +1,61 @@ +validFrom = $validFrom; + $this->validTo = $validTo; + } + + /** + * @param array $data + * + * @throws Exception + * @internal + */ + public static function createFromApi(array $data): self + { + return new self( + new DateTime($data['valid_from']), + new DateTime($data['valid_to']), + ); + } + + /** + * @return array + */ + public function getArrayForApi(): array + { + return [ + 'valid_from' => $this->getValidFrom()->format(InputDataUtil::DATE_TIME_FORMAT), + 'valid_to' => $this->getValidTo()->format(InputDataUtil::DATE_TIME_FORMAT), + ]; + } + + public function getValidFrom(): DateTimeInterface + { + return $this->validFrom; + } + + public function getValidTo(): DateTimeInterface + { + return $this->validTo; + } + +} diff --git a/src/SupplyDelay/SupplyDelayClient.php b/src/SupplyDelay/SupplyDelayClient.php new file mode 100644 index 0000000..04fe585 --- /dev/null +++ b/src/SupplyDelay/SupplyDelayClient.php @@ -0,0 +1,67 @@ +sendJson('GET', self::PARTNER)['data']); + } + + public function upsert(SupplyDelay $supplyDelay): SupplyDelay + { + return SupplyDelay::createFromApi( + $this->sendJson('POST', self::PARTNER, $supplyDelay->getArrayForApi())['data'] + ); + } + + public function delete(): void + { + $this->sendJson('DELETE', self::PARTNER); + } + + public function getForProduct(string $productId): SupplyDelay + { + return SupplyDelay::createFromApi($this->sendJson('GET', sprintf(self::PRODUCT, $productId))['data']); + } + + public function upsertForProduct(string $productId, SupplyDelay $supplyDelay): SupplyDelay + { + return SupplyDelay::createFromApi( + $this->sendJson('POST', sprintf(self::PRODUCT, $productId), $supplyDelay->getArrayForApi())['data'] + ); + } + + public function deleteForProduct(string $productId): void + { + $this->sendJson('DELETE', sprintf(self::PRODUCT, $productId)); + } + + public function getForVariant(string $productId, string $variantId): SupplyDelay + { + return SupplyDelay::createFromApi($this->sendJson('GET', sprintf(self::VARIANT, $productId, $variantId))['data']); + } + + public function upsertForVariant(string $productId, string $variantId, SupplyDelay $supplyDelay): SupplyDelay + { + return SupplyDelay::createFromApi( + $this->sendJson('POST', sprintf(self::VARIANT, $productId, $variantId), $supplyDelay->getArrayForApi())['data'] + ); + } + + public function deleteForVariant(string $productId, string $variantId): void + { + $this->sendJson('DELETE', sprintf(self::VARIANT, $productId, $variantId)); + } + +} diff --git a/tests/_data/.gitkeep b/tests/_data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/_output/.gitignore b/tests/_output/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/tests/_output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php new file mode 100644 index 0000000..170f7fa --- /dev/null +++ b/tests/_support/AcceptanceTester.php @@ -0,0 +1,27 @@ + + */ + protected $config = [ + 'timeout' => 20, + 'allow_redirects' => true, + ]; + + private string $staticRandomString; + + public static function getRandomString(int $length = 10): string + { + try { + return bin2hex(random_bytes($length)); + } catch (Exception $e) { + // just a simple fallback, but should never be needed + $max = 10 ^ $length; + + return (string) mt_rand($max / 10, $max - 1); + } + } + + public static function getRandomInt(int $length = 10): int + { + $min = (10 ^ $length) / 10; + $max = 10 ^ $length - 1; + + try { + return random_int($min, $max); + } catch (Exception $e) { + // just a simple fallback, but should never be needed + return mt_rand($min, $max); + } + } + + /** + * @param array $settings + */ + public function _beforeSuite($settings = []): void + { + $this->staticRandomString = $this->getRandomString(); + } + + public function getAuthenticator(): AuthMiddlewareInterface + { + return new ClientIdAuthenticator($this->config['client_id']); + } + + public function getGuzzleClient(): Client + { + $options = new MpApiClientOptions($this->getAuthenticator()); + $options->setBaseUri($this->config['base_uri']); + $options->setTimeout($this->config['timeout']); + $options->setAllowRedirects($this->config['allow_redirects']); + + return new Client($options->getGuzzleOptionsArray()); + } + + public function getStaticRandomString(): string + { + return $this->staticRandomString; + } + + /** + * Generic helper function, to assert correct paging was returned + */ + public function assertPaging(AbstractList $list, Filter $filter): void + { + $page = (int) (floor($filter->getOffset() / $filter->getLimit()) + 1); + + $this->assertInstanceOf(Paging::class, $list->getPaging()); + $this->assertGreaterOrEquals($page, $list->getPaging()->getPage()); + $this->assertLessThanOrEqual($filter->getLimit(), $list->getPaging()->getSize()); // LTE because if the total is less than max, size = total, not max size + $this->assertGreaterThanOrEqual(0, $list->getPaging()->getTotal()); + $this->assertGreaterThanOrEqual(1, $list->getPaging()->getPages()); + } + +} diff --git a/tests/_support/Helper/Unit.php b/tests/_support/Helper/Unit.php new file mode 100644 index 0000000..998218e --- /dev/null +++ b/tests/_support/Helper/Unit.php @@ -0,0 +1,13 @@ +client = new ArticleClient($I->getGuzzleClient(), 'article-client-cest'); + + // Basic Create and Delete operations are called on one product/variant with static id + // - all other methods MUST create their own and SHOULD depend on TestDeleteXxx to ensure their setup and teardown work + $this->productId = self::PRODUCT_PREFIX . $I->getStaticRandomString(); + $this->variantId = self::VARIANT_PREFIX . $I->getStaticRandomString(); + $this->variantProductId = self::VARIANT_PRODUCT_PREFIX . $I->getStaticRandomString(); + } + + /** + * @throws MpApiException + */ + public function testListProducts(FunctionalTester $I): void + { + // used only for testing to set page size, because product endpoint ignores page_size query param + $filter = new Filter(); + $filter->setLimit(1000); + + $products = $this->client->listProducts(null); + $products->disableAutoload(); + + $I->assertInstanceOf(BasicProductList::class, $products); + $I->assertPaging($products, $filter); + + foreach ($products as $product) { + $this->assertBasicProductTypes($I, $product); + } + } + + /** + * @throws MpApiException + */ + public function testListProductsFiltered(FunctionalTester $I): void + { + $categoryId = 'NA020'; + + $filter = new Filter(); + $filter->addFilterItem(FilterItem::create('_category_id', $categoryId, FilterOperatorEnum::EQUAL())); + + $products = $this->client->listProducts($filter); + $products->enableAutoload(); + + $I->assertInstanceOf(BasicProductList::class, $products); + $I->assertPaging($products, $filter); + + $I->assertGreaterThanOrEqual(1, $products->getPaging()->getTotal()); + + foreach ($products as $product) { + $this->assertBasicProductTypes($I, $product); + $I->assertEquals($categoryId, $product->getCategoryId()); + } + } + + /** + * @throws MpApiException + */ + public function testCreateProduct(FunctionalTester $I): void + { + $product = $this->getNewProductRequest(); + $product->setId($this->productId); + $product->setPartnerTitle('custom partner title'); + $product->setBrandId('100TEST'); + + $productFromApi = $this->client->createProduct($product); + $this->assertProductValues($I, $product, $productFromApi); + } + + /** + * @depends testCreateProduct + * @throws MpApiException + */ + public function testDeleteProduct(): void + { + $this->client->deleteProduct($this->productId); + } + + public function testGetProductNotFound(FunctionalTester $I): void + { + $I->expectThrowable(NotFoundException::class, fn() => $this->client->getProduct(Functional::getRandomString())); + } + + /** + * @depends testDeleteProduct + * @throws MpApiException + */ + public function testGetProduct(FunctionalTester $I): void + { + // Setup + $product = $this->getNewProductRequest(); + $this->client->createProduct($product); + + // Execution + $productFromApi = $this->client->getProduct($product->getId()); + + // Assertions + $this->assertProductValues($I, $product, $productFromApi); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteProduct + * @throws MpApiException + */ + public function testUpdateProduct(FunctionalTester $I): void + { + // Setup + $product = $this->getNewProductRequest(); + $this->client->createProduct($product); + + // Execution + $product->setPartnerTitle('updated partner title'); + $product->setFreeDelivery(true); + $product->setDeliveryDelay(5); + + $this->client->updateProduct($product); + + // Assertions + $productDetail = $this->client->getProduct($product->getId()); + $this->assertProductValues($I, $product, $productDetail); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteProduct + * @throws MpApiException + */ + public function testGetProductAvailability(FunctionalTester $I): void + { + // Setup + $product = $this->getNewProductRequest(); + $this->client->createProduct($product); + + // Execution + $availability = $this->client->getProductAvailability($product->getId()); + + // Assertions + $I->assertEquals($product->getAvailability(), $availability); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteProduct + * @throws MpApiException + */ + public function testGetProductPricing(FunctionalTester $I): void + { + // Setup + $product = $this->getNewProductRequest(); + $product->setRrp(100); + // $product->setPurchasePrice(50); // 0 should be returned if not set + + $this->client->createProduct($product); + + // Execution + $pricingFromApi = $this->client->getProductPricing($product->getId()); + + // Assertions + $I->assertEquals($product->getPrice(), $pricingFromApi->getPrice()); + $I->assertEquals($product->getRrp(), $pricingFromApi->getRrp()); + $I->assertEquals(0.0, $pricingFromApi->getPurchasePrice()); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteProduct + * @throws MpApiException + */ + public function testUpdateProductPricing(FunctionalTester $I): void + { + // Setup + $product = $this->getNewProductRequest(); + $this->client->createProduct($product); + + // Execution + $pricing = new Pricing(75, 100, 50); + $this->client->updateProductPricing($product->getId(), $pricing); + + // Assertions + $pricingFromApi = $this->client->getProductPricing($product->getId()); + $I->assertEquals($pricing->getPrice(), $pricingFromApi->getPrice()); + $I->assertEquals($pricing->getRrp(), $pricingFromApi->getRrp()); + $I->assertEquals($pricing->getPurchasePrice(), $pricingFromApi->getPurchasePrice()); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testCreateProduct + * @throws MpApiException + * @throws JsonException + */ + public function testCreateVariantProduct(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $variant->setId($this->variantId); + + $product = $this->getNewProductRequest(); + $product->setId($this->variantProductId); + $product->getVariants()->add($variant); + + // Execution + $productFromApi = $this->client->createProduct($product); + $variantFromApi = $productFromApi->getVariants()->current(); + + // Assertions + $this->assertProductValues($I, $product, $productFromApi); + $this->assertVariantValues($I, $variant, $variantFromApi); + } + + /** + * @depends testCreateVariantProduct + * @throws MpApiException + * @throws JsonException + */ + public function testCreateVariant(FunctionalTester $I): void + { + $variant = $this->getNewVariantRequest(); + + $variantFromApi = $this->client->createVariant($this->variantProductId, $variant); + $this->assertVariantValues($I, $variant, $variantFromApi); + } + + /** + * @depends testCreateVariant + * @throws MpApiException + */ + public function testDeleteVariant(): void + { + $this->client->deleteVariant($this->variantProductId, $this->variantId); + $this->client->deleteProduct($this->variantProductId); + } + + /** + * @depends testCreateVariantProduct + * @throws MpApiException + * @throws JsonException + */ + public function testListProductVariants(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $variants = $this->client->listProductVariants($product->getId(), null); + + $I->assertInstanceOf(BasicVariantList::class, $variants); + $I->assertPaging($variants, new Filter()); + + foreach ($variants as $variant) { + $I->assertIsString($variant->getId()); + $I->assertIsInt($variant->getProductId()); + $I->assertIsInt($variant->getVariantId()); + $I->assertIsString($variant->getTitle()); + $I->assertInstanceOf(StatusEnum::class, $variant->getStatus()); + $I->assertIsInt($variant->getInStock()); + $I->assertIsFloat($variant->getPrice()); + $I->assertIsFloat($variant->getPurchasePrice()); + $I->assertIsFloat($variant->getRrp()); + } + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + public function testGetVariantNotFound(FunctionalTester $I): void + { + $I->expectThrowable(NotFoundException::class, fn() => $this->client->getVariant(Functional::getRandomString(), Functional::getRandomString())); + } + + /** + * @depends testDeleteVariant + * @throws MpApiException + * @throws JsonException + */ + public function testGetVariant(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $variantFromApi = $this->client->getVariant($product->getId(), $variant->getId()); + + // Assertions + $this->assertVariantValues($I, $variant, $variantFromApi); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteVariant + * @throws MpApiException + */ + public function testUpdateVariant(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $variant->setMallboxAllowed(true); + $variant->setFreeDelivery(true); + $variant->setDeliveryDelay(5); + + $this->client->updateVariant($product->getId(), $variant); + + // Assertions + $variantDetail = $this->client->getVariant($product->getId(), $variant->getId()); + + $I->assertEquals(5, $variantDetail->getDeliveryDelay()); + $I->assertEquals(true, $variantDetail->hasFreeDelivery()); + $I->assertEquals(true, $variantDetail->hasMallboxAllowed()); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteVariant + * @throws MpApiException + */ + public function testGetVariantAvailability(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $availability = $this->client->getVariantAvailability($product->getId(), $variant->getId()); + + // Assertions + $I->assertEquals($variant->getAvailability(), $availability); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteVariant + * @throws MpApiException + */ + public function testGetVariantPricing(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $variant->setRrp(100); + + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $pricingFromApi = $this->client->getVariantPricing($product->getId(), $variant->getId()); + + // Assertions + $I->assertEquals($variant->getPrice(), $pricingFromApi->getPrice()); + $I->assertEquals($variant->getRrp(), $pricingFromApi->getRrp()); + $I->assertEquals(0.0, $pricingFromApi->getPurchasePrice()); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteVariant + * @throws MpApiException + */ + public function testUpdateVariantPricing(FunctionalTester $I): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $variant->setRrp(100); + + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $pricing = new Pricing(75, 100, 50); + $this->client->updateVariantPricing($product->getId(), $variant->getId(), $pricing); + + // Assertions + $pricingFromApi = $this->client->getVariantPricing($product->getId(), $variant->getId()); + + $I->assertEquals($pricing->getPrice(), $pricingFromApi->getPrice()); + $I->assertEquals($pricing->getRrp(), $pricingFromApi->getRrp()); + $I->assertEquals($pricing->getPurchasePrice(), $pricingFromApi->getPurchasePrice()); + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @depends testDeleteProduct + * @depends testCreateVariantProduct + * @throws MpApiException + */ + public function testUpdateBatchAvailability(): void + { + // Setup + $variant = $this->getNewVariantRequest(); + $product = $this->getNewProductRequest(); + $product->getVariants()->add($variant); + + $this->client->createProduct($product); + + // Execution + $availability = new BatchAvailabilityIterator( + new BatchAvailability($product->getId(), StatusEnum::ACTIVE(), 2), + new BatchAvailability($variant->getId(), StatusEnum::ACTIVE(), 2), + ); + $this->client->updateBatchAvailability($availability); + + // No assertions, because batch availability is asynchronous + + // Cleanup + $this->client->deleteProduct($product->getId()); + } + + /** + * @deapends testDeleteProduct + * @throws MpApiException + */ + public function _testActivateSelectedProducts(FunctionalTester $I): void + { + // All products created by active partner are automatically activated and inactive product can not be created. + // There is no way to write functional test for this method. + + // Setup + $product1 = $this->getNewProductRequest(); + $product1->setAvailability(new Availability(StatusEnum::INACTIVE(), 1)); + + $product2 = $this->getNewProductRequest(); + $product2->setAvailability(new Availability(StatusEnum::INACTIVE(), 1)); + + $product1FromApi = $this->client->createProduct($product1); + $product2FromApi = $this->client->createProduct($product2); + + // Execution + $this->client->activateSelectedProducts($product1->getId(), $product2->getId()); + + // Assertions + $I->assertEquals(StatusEnum::INACTIVE(), $product1FromApi->getAvailability()->getStatus()); + $I->assertEquals(StatusEnum::INACTIVE(), $product2FromApi->getAvailability()->getStatus()); + + $product1Availability = $this->client->getProductAvailability($product1->getId()); + $product2Availability = $this->client->getProductAvailability($product2->getId()); + + $I->assertEquals(StatusEnum::ACTIVE(), $product1Availability->getStatus()); + $I->assertEquals(StatusEnum::ACTIVE(), $product2Availability->getStatus()); + + // Cleanup + $this->client->deleteProduct($product1->getId()); + $this->client->deleteProduct($product2->getId()); + } + + /** + * @depends testDeleteProduct + * @throws MpApiException + */ + public function _testActivateAllProducts(FunctionalTester $I): void + { + // All products created by active partner are automatically activated and inactive product can not be created. + // There is no way to write functional test for this method. + + // Setup + $product1 = $this->getNewProductRequest(); + $product1->setAvailability(new Availability(StatusEnum::INACTIVE(), 1)); + + $product2 = $this->getNewProductRequest(); + $product2->setAvailability(new Availability(StatusEnum::INACTIVE(), 1)); + + $product1FromApi = $this->client->createProduct($product1); + $product2FromApi = $this->client->createProduct($product2); + + // Execution + $this->client->activateAllProducts(); + + // Assertions + $I->assertEquals(StatusEnum::INACTIVE(), $product1FromApi->getAvailability()->getStatus()); + $I->assertEquals(StatusEnum::INACTIVE(), $product2FromApi->getAvailability()->getStatus()); + + $product1Availability = $this->client->getProductAvailability($product1->getId()); + $product2Availability = $this->client->getProductAvailability($product2->getId()); + + $I->assertEquals(StatusEnum::ACTIVE(), $product1Availability->getStatus()); + $I->assertEquals(StatusEnum::ACTIVE(), $product2Availability->getStatus()); + + // Cleanup + $this->client->deleteProduct($product1->getId()); + $this->client->deleteProduct($product2->getId()); + } + + /* + * Assertion helpers + */ + + private function assertBasicProductTypes(FunctionalTester $I, BasicProduct $product): void + { + $I->assertIsString($product->getId()); + $I->assertIsInt($product->getProductId()); + $I->assertIsString($product->getTitle()); + $I->assertInstanceOf(StatusEnum::class, $product->getStatus()); + $I->assertInstanceOf(ProductStageEnum::class, $product->getStage()); + $I->assertIsInt($product->getInStock()); + $I->assertIsString($product->getCategoryId()); + $I->assertIsFloat($product->getPrice()); + $I->assertIsFloat($product->getPurchasePrice()); + $I->assertIsFloat($product->getRrp()); + $I->assertIsInt($product->getVariantsCount()); + $I->assertIsBool($product->hasVariants()); + } + + private function assertProductValues(FunctionalTester $I, ProductRequest $productRequest, Product $product): void + { + $I->assertGreaterThan(0, $product->getArticleId()); + // check fixture fields + $I->assertEquals($productRequest->getId(), $product->getId()); + $I->assertEquals($productRequest->getTitle(), $product->getTitle()); + $I->assertEquals($productRequest->getShortDesc(), $product->getShortDesc()); + $I->assertEquals($productRequest->getLongDesc(), $product->getLongDesc()); + $I->assertEquals($productRequest->getCategoryId(), $product->getCategoryId()); + $I->assertEquals($productRequest->getVat(), $product->getVat()); + $I->assertEquals($productRequest->getPriority(), $product->getPriority()); + // check custom fields + $I->assertEquals($productRequest->getPrice(), $product->getPrice()); + $I->assertEquals($productRequest->getPartnerTitle(), $product->getPartnerTitle()); + $I->assertEquals($productRequest->getBrandId(), $product->getBrandId()); + $I->assertEquals($productRequest->getAvailability()->getStatus(), $product->getAvailability()->getStatus()); + $I->assertEquals($productRequest->getAvailability()->getInStock(), $product->getAvailability()->getInStock()); + } + + /** + * @throws JsonException + */ + private function assertVariantValues(FunctionalTester $I, VariantRequest $variantRequest, Variant $variant): void + { + // API MUST return new articleId + $I->assertGreaterThan(0, $variant->getArticleId()); + // check fixture fields + $I->assertEquals($variantRequest->getId(), $variant->getId()); + $I->assertEquals($variantRequest->getTitle(), $variant->getTitle()); + $I->assertEquals($variantRequest->getShortDesc(), $variant->getShortDesc()); + $I->assertEquals($variantRequest->getLongDesc(), $variant->getLongDesc()); + $I->assertEquals($variantRequest->getPriority(), $variant->getPriority()); + $I->assertEquals(json_encode($variantRequest->getMedia(), JSON_THROW_ON_ERROR), json_encode($variant->getMedia(), JSON_THROW_ON_ERROR)); + // check custom fields + $I->assertEquals($variantRequest->getPrice(), $variant->getPrice()); + $I->assertEquals($variantRequest->getAvailability()->getStatus(), $variant->getAvailability()->getStatus()); + $I->assertEquals($variantRequest->getAvailability()->getInStock(), $variant->getAvailability()->getInStock()); + } + + /* + * Providers + */ + + private function getNewProductId(): string + { + return self::PRODUCT_PREFIX . Functional::getRandomString(); + } + + private function getNewVariantId(): string + { + return self::VARIANT_PREFIX . Functional::getRandomString(); + } + + /** + * Helper that returns minimal valid product with new unique product ID for testing + */ + private function getNewProductRequest(): ProductRequest + { + /** @var callable $productFn */ + $productFn = Fixtures::get('product'); + /** @var ProductRequest $product */ + $product = $productFn(); + $product->setId($this->getNewProductId()); + $product->setAvailability(Fixtures::get('article-availability')); + $product->setMedia(Fixtures::get('article-media')); + $product->setPrice(69); + + return $product; + } + + /** + * Helper that returns minimal valid variant with new unique variant ID for testing + */ + private function getNewVariantRequest(): VariantRequest + { + /** @var callable $variantFn */ + $variantFn = Fixtures::get('variant'); + /** @var VariantRequest $variant */ + $variant = $variantFn(); + $variant->setId($this->getNewVariantId()); + $variant->setAvailability(Fixtures::get('article-availability')); + + return $variant; + } + +} diff --git a/tests/functional/BrandClientCest.php b/tests/functional/BrandClientCest.php new file mode 100644 index 0000000..295c7b9 --- /dev/null +++ b/tests/functional/BrandClientCest.php @@ -0,0 +1,39 @@ +client = new BrandClient($I->getGuzzleClient(), 'brand-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testList(FunctionalTester $I): void + { + $brands = $this->client->list(); + + $I->assertInstanceOf(BrandIterator::class, $brands); + + foreach ($brands as $brand) { + $I->assertInstanceOf(Brand::class, $brand); + + $I->assertIsString($brand->getBrandId()); + $I->assertIsString($brand->getTitle()); + } + } + +} diff --git a/tests/functional/CategoryClientCest.php b/tests/functional/CategoryClientCest.php new file mode 100644 index 0000000..1a57795 --- /dev/null +++ b/tests/functional/CategoryClientCest.php @@ -0,0 +1,152 @@ +client = new CategoryClient($I->getGuzzleClient(), 'category-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testList(FunctionalTester $I): void + { + $categories = $this->client->list(); + + $I->assertInstanceOf(CategoryIterator::class, $categories); + + foreach ($categories as $category) { + $I->assertInstanceOf(Category::class, $category); + $I->assertIsString($category->getCategoryId()); + $I->assertIsString($category->getTitle()); + } + } + + /** + * @throws MpApiException + */ + public function testGetParameters(FunctionalTester $I): void + { + // Expected parameters to be returned, with all data types set correctly + $parametersArr = Fixtures::get('categoryParameters'); + + $parameters = $this->client->getParameters(Fixtures::get('categoryId')); + + foreach ($parameters as $idx => $parameter) { + $I->assertInstanceOf(Parameter::class, $parameter); + $I->assertInstanceOf(ParameterValueIterator::class, $parameter->getValues()); + + $I->assertEquals($parametersArr[$idx]['param_id'], $parameter->getParamId()); + $I->assertEquals($parametersArr[$idx]['title'], $parameter->getTitle()); + $I->assertEquals($parametersArr[$idx]['unit'], $parameter->getUnit()); + $I->assertEquals($parametersArr[$idx]['unit'] !== '', $parameter->hasUnit()); + + foreach ($parameter->getValues() as $valIdx => $value) { + $I->assertInstanceOf(ParameterValue::class, $value); + $I->assertEquals($parametersArr[$idx]['values'][$valIdx]['value'], $value->getValue()); + $I->assertEquals($parametersArr[$idx]['values'][$valIdx]['text'], $value->getText()); + } + } + } + + /** + * @throws MpApiException + */ + public function testTree(FunctionalTester $I): void + { + $tree = $this->client->tree(ShopIdEnum::CZ10MA()); + + $I->assertInstanceOf(TreeItemIterator::class, $tree); + $this->assertTreeItems($I, $tree); + } + + /* + * Assertion helpers + */ + + private function assertTreeItems(FunctionalTester $I, TreeItemIterator $items): void + { + foreach ($items as $item) { + $I->assertInstanceOf(TreeItem::class, $item); + + $I->assertIsString($item->getTitle()); + $I->assertIsBool($item->isCategoryVisible()); + $I->assertInstanceOf(TreeItemIterator::class, $item->getItems()); + $I->assertInstanceOf(TreeMenuItemIterator::class, $item->getMenuItems()); + + $this->assertTreeItems($I, $item->getItems()); + $this->assertTreeMenuItems($I, $item->getMenuItems()); + } + } + + private function assertTreeMenuItems(FunctionalTester $I, TreeMenuItemIterator $menuItems): void + { + foreach ($menuItems as $menuItem) { + $I->assertInstanceOf(TreeMenuItem::class, $menuItem); + + $I->assertIsInt($menuItem->getMenuItemId()); + $I->assertIsString($menuItem->getTitle()); + $I->assertIsString($menuItem->getUrl()); + $I->assertInstanceOf(TreeSapCategoryIterator::class, $menuItem->getSapCategories()); + + $this->assertTreeSapCategories($I, $menuItem->getSapCategories()); + } + } + + private function assertTreeSapCategories(FunctionalTester $I, TreeSapCategoryIterator $sapCategories): void + { + foreach ($sapCategories as $sapCategory) { + $I->assertInstanceOf(TreeSapCategory::class, $sapCategory); + + $I->assertIsString($sapCategory->getOperator()); + $I->assertIsString($sapCategory->getSegment()); + $I->assertIsString($sapCategory->getProductTypeId()); + $I->assertInstanceOf(TreeMenuConstraintIterator::class, $sapCategory->getMenuConstraints()); + + $this->assertTreeMenuConstraints($I, $sapCategory->getMenuConstraints()); + } + } + + private function assertTreeMenuConstraints(FunctionalTester $I, TreeMenuConstraintIterator $menuConstraints): void + { + foreach ($menuConstraints as $menuConstraint) { + $I->assertInstanceOf(TreeMenuConstraint::class, $menuConstraint); + + $I->assertIsString($menuConstraint->getOperator()); + $I->assertIsString($menuConstraint->getParamId()); + $I->assertIsInt($menuConstraint->getClass()); + $I->assertIsString($menuConstraint->getValue1()); + + if ($menuConstraint->getValue2() !== null) { + $I->assertIsString($menuConstraint->getValue2()); + } + } + } + +} diff --git a/tests/functional/ChecksClientCest.php b/tests/functional/ChecksClientCest.php new file mode 100644 index 0000000..25673f2 --- /dev/null +++ b/tests/functional/ChecksClientCest.php @@ -0,0 +1,66 @@ +client = new ChecksClient($I->getGuzzleClient(), 'checks-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testGetMediaErrors(FunctionalTester $I): void + { + $mediaErrors = $this->client->getMediaErrors(); + + $I->assertInstanceOf(ErrorIterator::class, $mediaErrors); + + $this->assertCheckErrors($I, $mediaErrors); + } + + /** + * @throws MpApiException + */ + public function testGetDeliveryErrors(FunctionalTester $I): void + { + $mediaErrors = $this->client->getDeliveryErrors(); + + $I->assertInstanceOf(ErrorIterator::class, $mediaErrors); + + $this->assertCheckErrors($I, $mediaErrors); + } + + /* + * Assertion helpers + */ + + private function assertCheckErrors(FunctionalTester $I, ErrorIterator $errors): void + { + foreach ($errors as $error) { + $I->assertInstanceOf(Error::class, $error); + + $I->assertIsString($error->getCode()); + $I->assertIsString($error->getValue()); + $I->assertIsString($error->getMsg()); + $I->assertIsString($error->getAttribute()); + + foreach ($error->getArticles() as $article) { + $I->assertIsString($article); + } + } + } + +} diff --git a/tests/functional/FinancialClientCest.php b/tests/functional/FinancialClientCest.php new file mode 100644 index 0000000..82014c2 --- /dev/null +++ b/tests/functional/FinancialClientCest.php @@ -0,0 +1,278 @@ +client = new FinancialClient($I->getGuzzleClient(), 'financial-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testListInvoices(FunctionalTester $I): void + { + $invoices = $this->client->listInvoices(null); + $invoices->enableAutoload(); + + $I->assertInstanceOf(InvoiceList::class, $invoices); + $I->assertPaging($invoices, new Filter()); + + foreach ($invoices as $invoice) { + $this->assertInvoiceClasses($I, $invoice); + $I->assertEquals('3000', $invoice->getPartner()); + } + } + + /** + * @throws MpApiException + */ + public function testGetInvoice(FunctionalTester $I): void + { + // Expected invoice to be returned, with all data types set correctly + $invoiceArr = Fixtures::get('invoice'); + + $invoice = $this->client->getInvoice(Fixtures::get('invoiceId')); + $this->assertInvoiceClasses($I, $invoice); + + $I->assertEquals($invoiceArr['invoiceNumber'], $invoice->getInvoiceNumber()); + $I->assertEquals($invoiceArr['partner'], $invoice->getPartner()); + $I->assertEquals($invoiceArr['total'], $invoice->getTotal()); + $I->assertEquals($invoiceArr['createdAt'], $invoice->getCreatedAt()); + $I->assertEquals($invoiceArr['deliveryAt'], $invoice->getDeliveryAt()); + $I->assertEquals($invoiceArr['dueDate'], $invoice->getDueDate()); + $I->assertEquals($invoiceArr['originDocumentId'] ?? null, $invoice->getOriginDocumentId()); + $I->assertEquals($invoiceArr['currency'], $invoice->getCurrency()); + $I->assertEquals($invoiceArr['filePath'], $invoice->getFilePath()); + $I->assertEquals($invoiceArr['total'], $invoice->getTotal()); + $I->assertEquals($invoiceArr['note'], $invoice->getNote()); + $I->assertEquals($invoiceArr['purchNoC'], $invoice->getPurchNoC()); + $I->assertEquals($invoiceArr['invoiceType'], $invoice->getInvoiceType()); + $I->assertEquals($invoiceArr['invoiceIndicator'], $invoice->getInvoiceIndicator()); + $I->assertEquals($invoiceArr['documentType'], $invoice->getDocumentType()); + $I->assertEquals($invoiceArr['invoiceTypeTag'], $invoice->getInvoiceTypeTag()); + + $I->assertEquals($invoiceArr['supplier']['name'], $invoice->getSupplier()->getName()); + $I->assertEquals($invoiceArr['supplier']['registrationNumber'], $invoice->getSupplier()->getRegistrationNumber()); + $I->assertEquals($invoiceArr['supplier']['taxIdentification'], $invoice->getSupplier()->getTaxIdentification()); + $I->assertEquals($invoiceArr['supplier']['vatNumber'], $invoice->getSupplier()->getVatNumber()); + $I->assertEquals($invoiceArr['supplier']['note'], $invoice->getSupplier()->getNote()); + + $I->assertEquals($invoiceArr['supplier']['address']['street'], $invoice->getSupplier()->getAddress()->getStreet()); + $I->assertEquals($invoiceArr['supplier']['address']['city'], $invoice->getSupplier()->getAddress()->getCity()); + $I->assertEquals($invoiceArr['supplier']['address']['zip'], $invoice->getSupplier()->getAddress()->getZip()); + $I->assertEquals($invoiceArr['supplier']['address']['country'], $invoice->getSupplier()->getAddress()->getCountry()); + + $I->assertEquals($invoiceArr['supplier']['bank']['iban'], $invoice->getSupplier()->getBank()->getIban()); + $I->assertEquals($invoiceArr['supplier']['bank']['swift'], $invoice->getSupplier()->getBank()->getSwift()); + $I->assertEquals($invoiceArr['supplier']['bank']['bankName'], $invoice->getSupplier()->getBank()->getBankName()); + $I->assertEquals($invoiceArr['supplier']['bank']['bankAccount'], $invoice->getSupplier()->getBank()->getBankAccount()); + + $I->assertEquals($invoiceArr['customer']['name'], $invoice->getCustomer()->getName()); + $I->assertEquals($invoiceArr['customer']['registrationNumber'], $invoice->getCustomer()->getRegistrationNumber()); + $I->assertEquals($invoiceArr['customer']['taxIdentification'], $invoice->getCustomer()->getTaxIdentification()); + $I->assertEquals($invoiceArr['customer']['vatNumber'], $invoice->getCustomer()->getVatNumber()); + $I->assertEquals($invoiceArr['customer']['note'], $invoice->getCustomer()->getNote()); + + $I->assertEquals($invoiceArr['customer']['address']['street'], $invoice->getCustomer()->getAddress()->getStreet()); + $I->assertEquals($invoiceArr['customer']['address']['city'], $invoice->getCustomer()->getAddress()->getCity()); + $I->assertEquals($invoiceArr['customer']['address']['zip'], $invoice->getCustomer()->getAddress()->getZip()); + $I->assertEquals($invoiceArr['customer']['address']['country'], $invoice->getCustomer()->getAddress()->getCountry()); + + $I->assertEquals($invoiceArr['items'][0]['id'], $invoice->getItems()->current()->getId()); + $I->assertEquals($invoiceArr['items'][0]['articleId'], $invoice->getItems()->current()->getArticleId()); + $I->assertEquals($invoiceArr['items'][0]['title'], $invoice->getItems()->current()->getTitle()); + $I->assertEquals($invoiceArr['items'][0]['titleEn'], $invoice->getItems()->current()->getTitleEn()); + $I->assertEquals($invoiceArr['items'][0]['quantity'], $invoice->getItems()->current()->getQuantity()); + $I->assertEquals($invoiceArr['items'][0]['unit'], $invoice->getItems()->current()->getUnit()); + $I->assertEquals($invoiceArr['items'][0]['unitPrice'], $invoice->getItems()->current()->getUnitPrice()); + $I->assertEquals($invoiceArr['items'][0]['vatPrice'], $invoice->getItems()->current()->getVatPrice()); + $I->assertEquals($invoiceArr['items'][0]['priceWithoutVat'], $invoice->getItems()->current()->getPriceWithoutVat()); + $I->assertEquals($invoiceArr['items'][0]['vatRate'], $invoice->getItems()->current()->getVatRate()); + $I->assertEquals($invoiceArr['items'][0]['totalPrice'], $invoice->getItems()->current()->getTotalPrice()); + $I->assertEquals($invoiceArr['items'][0]['orderId'], $invoice->getItems()->current()->getOrderId()); + + $I->assertEquals($invoiceArr['taxRecap']['total'], $invoice->getTaxRecap()->getTotal()); + + $I->assertEquals($invoiceArr['taxRecap']['taxes'][0]['tax'], $invoice->getTaxRecap()->getTaxes()->current()->getTax()); + $I->assertEquals($invoiceArr['taxRecap']['taxes'][0]['base'], $invoice->getTaxRecap()->getTaxes()->current()->getBase()); + $I->assertEquals($invoiceArr['taxRecap']['taxes'][0]['total'], $invoice->getTaxRecap()->getTaxes()->current()->getTotal()); + $I->assertEquals($invoiceArr['taxRecap']['taxes'][0]['price'], $invoice->getTaxRecap()->getTaxes()->current()->getPrice()); + } + + /** + * @throws MpApiException + */ + public function testDownloadInvoice(FunctionalTester $I): void + { + $invoiceId = Fixtures::get('invoiceId'); + + $response = $this->client->downloadInvoice($invoiceId); + $I->assertInstanceOf(ResponseInterface::class, $response); + $I->assertEquals(['application/pdf'], $response->getHeader('content-type')); + $I->assertEquals(['13'], $response->getHeader('content-length')); + } + + /** + * @throws MpApiException + */ + public function testListOffsets(FunctionalTester $I): void + { + $offsets = $this->client->listOffsets(null); + $offsets->disableAutoload(); + + $I->assertInstanceOf(OffsetList::class, $offsets); + $I->assertPaging($offsets, new Filter()); + + foreach ($offsets as $offset) { + $this->assertOffsetClasses($I, $offset); + $I->assertEquals('3000', $offset->getPartner()); + } + } + + /** + * @throws MpApiException + */ + public function testGetOffset(FunctionalTester $I): void + { + // Expected offset to be returned, with all data types set correctly + $offsetArr = Fixtures::get('offset'); + + $offset = $this->client->getOffset(Fixtures::get('offsetId')); + $this->assertOffsetClasses($I, $offset); + + $I->assertEquals($offsetArr['partner'], $offset->getPartner()); + $I->assertEquals($offsetArr['documentNumber'], $offset->getDocumentNumber()); + $I->assertEquals($offsetArr['createdAt'], $offset->getCreatedAt()); + $I->assertEquals($offsetArr['dueDate'], $offset->getDueDate()); + $I->assertEquals($offsetArr['currency'], $offset->getCurrency()); + $I->assertEquals($offsetArr['diffPrice'], $offset->getDiffPrice()); + $I->assertEquals($offsetArr['variableSymbol'], $offset->getVariableSymbol()); + + $I->assertEquals($offsetArr['supplier']['name'], $offset->getSupplier()->getName()); + $I->assertEquals($offsetArr['supplier']['registrationNumber'], $offset->getSupplier()->getRegistrationNumber()); + $I->assertEquals($offsetArr['supplier']['taxIdentification'], $offset->getSupplier()->getTaxIdentification()); + $I->assertEquals($offsetArr['supplier']['vatNumber'], $offset->getSupplier()->getVatNumber()); + $I->assertEquals($offsetArr['supplier']['note'], $offset->getSupplier()->getNote()); + + $I->assertEquals($offsetArr['supplier']['address']['street'], $offset->getSupplier()->getAddress()->getStreet()); + $I->assertEquals($offsetArr['supplier']['address']['city'], $offset->getSupplier()->getAddress()->getCity()); + $I->assertEquals($offsetArr['supplier']['address']['zip'], $offset->getSupplier()->getAddress()->getZip()); + $I->assertEquals($offsetArr['supplier']['address']['country'], $offset->getSupplier()->getAddress()->getCountry()); + + $I->assertEquals($offsetArr['customer']['name'], $offset->getCustomer()->getName()); + $I->assertEquals($offsetArr['customer']['registrationNumber'], $offset->getCustomer()->getRegistrationNumber()); + $I->assertEquals($offsetArr['customer']['taxIdentification'], $offset->getCustomer()->getTaxIdentification()); + $I->assertEquals($offsetArr['customer']['vatNumber'], $offset->getCustomer()->getVatNumber()); + $I->assertEquals($offsetArr['customer']['note'], $offset->getCustomer()->getNote()); + + $I->assertEquals($offsetArr['customer']['address']['street'], $offset->getCustomer()->getAddress()->getStreet()); + $I->assertEquals($offsetArr['customer']['address']['city'], $offset->getCustomer()->getAddress()->getCity()); + $I->assertEquals($offsetArr['customer']['address']['zip'], $offset->getCustomer()->getAddress()->getZip()); + $I->assertEquals($offsetArr['customer']['address']['country'], $offset->getCustomer()->getAddress()->getCountry()); + + $I->assertEquals($offsetArr['invoices'][0]['id'], $offset->getInvoices()->current()->getId()); + $I->assertEquals($offsetArr['invoices'][0]['created'], $offset->getInvoices()->current()->getCreated()); + $I->assertEquals($offsetArr['invoices'][0]['dueDate'], $offset->getInvoices()->current()->getDueDate()); + $I->assertEquals($offsetArr['invoices'][0]['sumPrice'], $offset->getInvoices()->current()->getSumPrice()); + $I->assertEquals($offsetArr['invoices'][0]['offsetPrice'], $offset->getInvoices()->current()->getOffsetPrice()); + $I->assertEquals($offsetArr['invoices'][0]['remainPrice'], $offset->getInvoices()->current()->getRemainPrice()); + $I->assertEquals($offsetArr['invoices'][0]['currency'], $offset->getInvoices()->current()->getCurrency()); + $I->assertEquals($offsetArr['invoices'][0]['purchNoC'], $offset->getInvoices()->current()->getPurchNoC()); + $I->assertEquals($offsetArr['invoices'][0]['note'], $offset->getInvoices()->current()->getNote()); + $I->assertEquals($offsetArr['invoices'][0]['invoiceNumber'], $offset->getInvoices()->current()->getInvoiceNumber()); + + $I->assertEquals($offsetArr['orders'][0]['id'], $offset->getOrders()->current()->getId()); + $I->assertEquals($offsetArr['orders'][0]['created'], $offset->getOrders()->current()->getCreated()); + $I->assertEquals($offsetArr['orders'][0]['dueDate'], $offset->getOrders()->current()->getDueDate()); + $I->assertEquals($offsetArr['orders'][0]['sumPrice'], $offset->getOrders()->current()->getSumPrice()); + $I->assertEquals($offsetArr['orders'][0]['offsetPrice'], $offset->getOrders()->current()->getOffsetPrice()); + $I->assertEquals($offsetArr['orders'][0]['remainPrice'], $offset->getOrders()->current()->getRemainPrice()); + $I->assertEquals($offsetArr['orders'][0]['currency'], $offset->getOrders()->current()->getCurrency()); + + $I->assertEquals($offsetArr['attachment']['filename'], $offset->getAttachment()->getFilename()); + $I->assertEquals($offsetArr['attachment']['mime'], $offset->getAttachment()->getMime()); + } + + /** + * @throws MpApiException + */ + public function testDownloadOffset(FunctionalTester $I): void + { + $offsetId = Fixtures::get('offsetId'); + + $response = $this->client->downloadOffset($offsetId); + $I->assertInstanceOf(ResponseInterface::class, $response); + $I->assertEquals(['application/pdf'], $response->getHeader('content-type')); + $I->assertEquals(['15723'], $response->getHeader('content-length')); + } + + /* + * Assertion helpers + */ + + private function assertInvoiceClasses(FunctionalTester $I, Invoice $invoice): void + { + $I->assertInstanceOf(Invoice::class, $invoice); + $I->assertInstanceOf(ItemIterator::class, $invoice->getItems()); + + $I->assertInstanceOf(Supplier::class, $invoice->getSupplier()); + $I->assertInstanceOf(Bank::class, $invoice->getSupplier()->getBank()); + $I->assertInstanceOf(Address::class, $invoice->getSupplier()->getAddress()); + + $I->assertInstanceOf(Customer::class, $invoice->getCustomer()); + $I->assertInstanceOf(Address::class, $invoice->getCustomer()->getAddress()); + + $I->assertInstanceOf(TaxRecap::class, $invoice->getTaxRecap()); + $I->assertInstanceOf(TaxIterator::class, $invoice->getTaxRecap()->getTaxes()); + } + + private function assertOffsetClasses(FunctionalTester $I, Offset $offset): void + { + $I->assertInstanceOf(Offset::class, $offset); + + $I->assertInstanceOf(Supplier::class, $offset->getSupplier()); + $I->assertInstanceOf(Address::class, $offset->getSupplier()->getAddress()); + + $I->assertInstanceOf(Customer::class, $offset->getCustomer()); + $I->assertInstanceOf(Address::class, $offset->getCustomer()->getAddress()); + + $I->assertInstanceOf(InvoiceSimpleIterator::class, $offset->getInvoices()); + $I->assertInstanceOf(InvoiceSimple::class, $offset->getInvoices()->current()); + + $I->assertInstanceOf(OrderIterator::class, $offset->getOrders()); + $I->assertInstanceOf(Order::class, $offset->getOrders()->current()); + + $I->assertInstanceOf(Attachment::class, $offset->getAttachment()); + } + +} diff --git a/tests/functional/LabelClientCest.php b/tests/functional/LabelClientCest.php new file mode 100644 index 0000000..4985ae8 --- /dev/null +++ b/tests/functional/LabelClientCest.php @@ -0,0 +1,39 @@ +client = new LabelClient($I->getGuzzleClient(), 'labels-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testList(FunctionalTester $I): void + { + $labels = $this->client->list(); + + $I->assertInstanceOf(LabelIterator::class, $labels); + + foreach ($labels as $label) { + $I->assertInstanceOf(Label::class, $label); + + $I->assertIsString($label->getId()); + $I->assertIsString($label->getTitle()); + } + } + +} diff --git a/tests/functional/MpApiClientCest.php b/tests/functional/MpApiClientCest.php new file mode 100644 index 0000000..f992776 --- /dev/null +++ b/tests/functional/MpApiClientCest.php @@ -0,0 +1,52 @@ +getGuzzleClient(), 'mpapi-client-cest'); + $this->assertDomainClients($I, $client); + } + + public function testCreateFromOptions(FunctionalTester $I): void + { + $options = new MpApiClientOptions($I->getAuthenticator()); + $client = MpApiClient::createFromOptions('mpapi-client-cest', $options); + $this->assertDomainClients($I, $client); + } + + /* + * Assertion helpers + */ + + private function assertDomainClients(FunctionalTester $I, MpApiClientInterface $client): void + { + $I->assertInstanceOf(ArticleClientInterface::class, $client->article()); + $I->assertInstanceOf(BrandClientInterface::class, $client->brand()); + $I->assertInstanceOf(CategoryClientInterface::class, $client->category()); + $I->assertInstanceOf(FinancialClientInterface::class, $client->financial()); + $I->assertInstanceOf(ChecksClientInterface::class, $client->checks()); + $I->assertInstanceOf(LabelClientInterface::class, $client->label()); + $I->assertInstanceOf(OrderClientInterface::class, $client->orders()); + $I->assertInstanceOf(ShopClientInterface::class, $client->shop()); + $I->assertInstanceOf(SupplyDelayClientInterface::class, $client->supplyDelay()); + } + +} diff --git a/tests/functional/OrderClientCest.php b/tests/functional/OrderClientCest.php new file mode 100644 index 0000000..1c89a86 --- /dev/null +++ b/tests/functional/OrderClientCest.php @@ -0,0 +1,199 @@ +client = new OrderClient($I->getGuzzleClient(), 'order-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testStats(FunctionalTester $I): void + { + $stats = $this->client->stats(); + + $I->assertIsInt($stats->getBlocked()); + $I->assertIsInt($stats->getOpen()); + $I->assertIsInt($stats->getShipping()); + $I->assertIsInt($stats->getShipped()); + $I->assertIsInt($stats->getCancelled()); + $I->assertIsInt($stats->getDelivered()); + $I->assertIsInt($stats->getLost()); + } + + /** + * @throws MpApiException + */ + public function testList(FunctionalTester $I): void + { + $orders = $this->client->list(null); + + $I->assertInstanceOf(BasicOrderList::class, $orders); + $I->assertPaging($orders, new Filter()); + + foreach ($orders as $order) { + $I->assertIsInt($order->getId()); + $I->assertIsInt($order->getPurchaseId()); + $I->assertIsInt($order->getCustomerId()); + $I->assertIsString($order->getCustomer()); + $I->assertIsFloat($order->getCod()); + $I->assertIsString($order->getPaymentType()); + if ($order->getShipDate() !== null) { + $I->assertInstanceOf(DateTimeInterface::class, $order->getShipDate()); + } + if ($order->getTrackingNumber() !== null) { + $I->assertIsString($order->getTrackingNumber()); + } + if ($order->getTrackingUrl() !== null) { + $I->assertIsString($order->getTrackingUrl()); + } + if ($order->getDeliveredAt() !== null) { + $I->assertInstanceOf(DateTimeInterface::class, $order->getDeliveredAt()); + } + $I->assertInstanceOf(StatusEnum::class, $order->getStatus()); + $I->assertIsBool($order->isConfirmed()); + $I->assertIsBool($order->isTest()); + $I->assertIsBool($order->isMdp()); + } + } + + public function testGetNotFound(FunctionalTester $I): void + { + $I->expectThrowable(NotFoundException::class, fn() => $this->client->get(Functional::getRandomInt())); + } + + /** + * @throws MpApiException + */ + public function testGet(FunctionalTester $I): void + { + // Expected order to be returned, with all data types set correctly + $orderArr = Fixtures::get('order'); + + $order = $this->client->get(Fixtures::get('orderId')); + $this->assertOrderClasses($I, $order); + + $I->assertEquals($orderArr['id'], $order->getId()); + $I->assertEquals($orderArr['purchase_id'], $order->getPurchaseId()); + $I->assertEquals($orderArr['currency'], $order->getCurrency()); + $I->assertEquals($orderArr['delivery_price'], $order->getDeliveryPrice()); + $I->assertEquals($orderArr['cod_price'], $order->getCodPrice()); + $I->assertEquals($orderArr['cod'], $order->getCod()); + $I->assertEquals($orderArr['discount'], $order->getDiscount()); + $I->assertEquals($orderArr['payment_type'], $order->getPaymentType()); + $I->assertEquals($orderArr['delivery_method'], $order->getDeliveryMethod()); + $I->assertEquals($orderArr['delivery_method_id'], $order->getDeliveryMethodId()); + $I->assertEquals($orderArr['branch_id'] ?? null, $order->getBranchId()); + + $I->assertEquals($orderArr['branches']['overridden'], $order->getBranches()->isOverridden()); + $I->assertEquals($orderArr['branches']['branch_id'] ?? null, $order->getBranches()->getBranchId()); + $I->assertEquals($orderArr['branches']['branch_change_time'] ?? null, $order->getBranches()->getLastChange()); + $I->assertEquals($orderArr['branches']['secondary_branch_id'] ?? null, $order->getBranches()->getSecondaryBranchId()); + + $I->assertEquals($orderArr['tracking_number'] ?? null, $order->getTrackingNumber()); + $I->assertEquals($orderArr['tracking_url'] ?? null, $order->getTrackingUrl()); + + $I->assertEquals($orderArr['ship_date'] ?? null, $order->getShipDate()); + $I->assertEquals($orderArr['delivery_date'] ?? null, $order->getDeliveryDate()); + $I->assertEquals($orderArr['delivered_at'] ?? null, $order->getDeliveredAt()); + $I->assertEquals($orderArr['first_delivery_attempt'] ?? null, $order->getFirstDeliveryAttempt()); + + $I->assertEquals($orderArr['address']['customer_id'], $order->getCustomer()->getCustomerId()); + $I->assertEquals($orderArr['address']['name'], $order->getCustomer()->getName()); + $I->assertEquals($orderArr['address']['company'] ?? null, $order->getCustomer()->getCompany()); + $I->assertEquals($orderArr['address']['phone'], $order->getCustomer()->getPhone()); + $I->assertEquals($orderArr['address']['email'], $order->getCustomer()->getEmail()); + $I->assertEquals($orderArr['address']['street'], $order->getCustomer()->getStreet()); + $I->assertEquals($orderArr['address']['city'], $order->getCustomer()->getCity()); + $I->assertEquals($orderArr['address']['zip'], $order->getCustomer()->getZip()); + $I->assertEquals($orderArr['address']['country'], $order->getCustomer()->getCountry()); + + $I->assertEquals($orderArr['confirmed'], $order->isConfirmed()); + $I->assertEquals($orderArr['status'], $order->getStatus()); + + $I->assertEquals($orderArr['items'][0]['id'], $order->getItems()->current()->getId()); + $I->assertEquals($orderArr['items'][0]['article_id'], $order->getItems()->current()->getArticleId()); + $I->assertEquals($orderArr['items'][0]['quantity'], $order->getItems()->current()->getQuantity()); + $I->assertEquals($orderArr['items'][0]['price'], $order->getItems()->current()->getPrice()); + $I->assertEquals($orderArr['items'][0]['vat'], $order->getItems()->current()->getVat()); + $I->assertEquals($orderArr['items'][0]['commission'] ?? null, $order->getItems()->current()->getCommission()); + $I->assertEquals($orderArr['items'][0]['title'], $order->getItems()->current()->getTitle()); + $I->assertEquals($orderArr['items'][0]['serial_numbers'], $order->getItems()->current()->getSerialNumbers()); + + $I->assertEquals($orderArr['test'], $order->isTest()); + $I->assertEquals($orderArr['mdp'], $order->isMdp()); + $I->assertEquals($orderArr['ready_to_return'], $order->isReadyToReturn()); + $I->assertEquals($orderArr['shipped'] ?? null, $order->getShipped()); + $I->assertEquals($orderArr['open'] ?? null, $order->getOpen()); + $I->assertEquals($orderArr['blocked'] ?? null, $order->getBlocked()); + $I->assertEquals($orderArr['lost'] ?? null, $order->getLost()); + $I->assertEquals($orderArr['returned'] ?? null, $order->getReturned()); + $I->assertEquals($orderArr['cancelled'] ?? null, $order->getCancelled()); + $I->assertEquals($orderArr['delivered'] ?? null, $order->getDelivered()); + $I->assertEquals($orderArr['shipping'] ?? null, $order->getShipping()); + $I->assertEquals(0, $order->getUlozenkaStatusHistory()->count()); + $I->assertEquals($orderArr['ulozenka_status_history'], $order->getUlozenkaStatusHistory()->jsonSerialize()); + } + + public function _testConfirmOrder(): void + { + // TODO: implement me + } + + public function _testSetStatus(): void + { + // TODO: implement me + } + + public function _testSetTracking(): void + { + // TODO: implement me + } + + public function _testSetItemSerialNumbers(): void + { + // TODO: implement me + } + + public function _testCreateShippingLabels(): void + { + // TODO: implement me + } + + /* + * Assertion helpers + */ + + private function assertOrderClasses(FunctionalTester $I, Order $order): void + { + $I->assertInstanceOf(Order::class, $order); + $I->assertInstanceOf(Branches::class, $order->getBranches()); + $I->assertInstanceOf(Customer::class, $order->getCustomer()); + $I->assertInstanceOf(StatusEnum::class, $order->getStatus()); + $I->assertInstanceOf(ItemIterator::class, $order->getItems()); + } + +} diff --git a/tests/functional/ShopClientCest.php b/tests/functional/ShopClientCest.php new file mode 100644 index 0000000..2e6c4ba --- /dev/null +++ b/tests/functional/ShopClientCest.php @@ -0,0 +1,44 @@ +client = new ShopClient($I->getGuzzleClient(), 'shop-client-cest'); + } + + /** + * @throws MpApiException + */ + public function testList(FunctionalTester $I): void + { + $shops = $this->client->list(); + + $I->assertInstanceOf(ShopIterator::class, $shops); + + foreach ($shops as $shop) { + $I->assertInstanceOf(Shop::class, $shop); + + $I->assertInstanceOf(ShopIdEnum::class, $shop->getShopId()); + $I->assertIsString($shop->getCountryId()); + $I->assertIsString($shop->getName()); + $I->assertIsString($shop->getCurrencyIso()); + $I->assertIsString($shop->getCurrencySymbol()); + $I->assertIsString($shop->getUrl()); + } + } + +} diff --git a/tests/functional/SupplyDelayClientCest.php b/tests/functional/SupplyDelayClientCest.php new file mode 100644 index 0000000..12edfe9 --- /dev/null +++ b/tests/functional/SupplyDelayClientCest.php @@ -0,0 +1,108 @@ +client = new SupplyDelayClient($I->getGuzzleClient(), 'supply-delay-client-cest'); + } + + public function testGetNotFound(FunctionalTester $I): void + { + $I->expectThrowable(NotFoundException::class, fn() => $this->client->get()); + } + + /** + * @depends testGetNotFound + * @throws MpApiException + */ + public function testUpsert(FunctionalTester $I): void + { + $supplyDelay = new SupplyDelay( + (new DateTime('now'))->setTime(0, 0), // remove microseconds, because API does not use them and comparison would fail + (new DateTime('now + 1 month'))->setTime(0, 0), + ); + $supplyDelayFromApi = $this->client->upsert($supplyDelay); + $this->assertSupplyDelayClasses($I, $supplyDelayFromApi); + + $I->assertEquals($supplyDelay->getValidTo(), $supplyDelayFromApi->getValidTo()); + $I->assertEquals($supplyDelay->getValidFrom(), $supplyDelayFromApi->getValidFrom()); + } + + /** + * @depends testUpsert + * @throws MpApiException + */ + public function testGet(FunctionalTester $I): void + { + $supplyDelay = $this->client->get(); + $this->assertSupplyDelayClasses($I, $supplyDelay); + } + + /** + * @depends testGet + * @throws MpApiException + */ + public function testDelete(FunctionalTester $I): void + { + $this->client->delete(); + + $I->expectThrowable(NotFoundException::class, fn() => $this->client->get()); + } + + public function _testGetForProduct(): void + { + // TODO: implement me + } + + public function _testUpsertForProduct(): void + { + // TODO: implement me + } + + public function _testDeleteForProduct(): void + { + // TODO: implement me + } + + public function _testGetForVariant(): void + { + // TODO: implement me + } + + public function _testUpsertForVariant(): void + { + // TODO: implement me + } + + public function _testDeleteForVariant(): void + { + // TODO: implement me + } + + /* + * Assertion helpers + */ + + private function assertSupplyDelayClasses(FunctionalTester $I, SupplyDelay $supplyDelay): void + { + $I->assertInstanceOf(SupplyDelay::class, $supplyDelay); + $I->assertInstanceOf(DateTimeInterface::class, $supplyDelay->getValidFrom()); + $I->assertInstanceOf(DateTimeInterface::class, $supplyDelay->getValidTo()); + } + +} diff --git a/tests/functional/bootstrap.php b/tests/functional/bootstrap.php new file mode 100644 index 0000000..5f0251a --- /dev/null +++ b/tests/functional/bootstrap.php @@ -0,0 +1,301 @@ + [ + 'param_id' => 'MP_ATR_TEST1_CHAR', + 'title' => 'Market place test 1 znaky', + 'unit' => '', + 'values' => [], + ], + 'MP_ATR_TEST2_NUM' => [ + 'param_id' => 'MP_ATR_TEST2_NUM', + 'title' => 'Market place test 2 čísla', + 'unit' => '', + 'values' => [], + ], + ], +); + +Fixtures::add('invoiceId', '99991111'); +Fixtures::add( + 'invoice', + [ + 'invoiceNumber' => 99991111, + 'partner' => '3000', + 'createdAt' => new DateTime('2018-11-23 00:00:00'), + 'deliveryAt' => new DateTime('2018-11-26 00:00:00'), + 'dueDate' => new DateTime('2018-11-26 00:00:00'), + 'currency' => 'CZK', + 'supplier' => [ + 'bank' => [ + 'iban' => 'CZ8100161984000098459001', + 'swift' => 'GXCAMCZX', + 'bankName' => 'Česká spořitelna', + 'bankAccount' => '1000084488/0800', + ], + 'name' => 'Novak s.r.o', + 'registrationNumber' => '123456789', + 'taxIdentification' => 'CZ123456789', + 'vatNumber' => '3210480832', + 'note' => '', + 'address' => [ + 'street' => 'U Garáží 1', + 'city' => 'Praha', + 'zip' => '17001', + 'country' => 'CZ', + ], + ], + 'customer' => [ + 'name' => 'Internet Mall s.r.o', + 'registrationNumber' => '26204967', + 'taxIdentification' => 'CZ26204967', + 'vatNumber' => '3210480832', + 'note' => '', + 'address' => [ + 'street' => 'U Garáží 1', + 'city' => 'Praha', + 'zip' => '17001', + 'country' => 'CZ', + ], + ], + 'items' => [ + [ + 'id' => 'IDP29826', + 'articleId' => 1100051914, + 'title' => 'Produckt 1', + 'titleEn' => '', + 'quantity' => 1, + 'unit' => 'KS', + 'unitPrice' => 200.0, + 'vatPrice' => 0.0, + 'priceWithoutVat' => 200.2, + 'vatRate' => 21, + 'totalPrice' => 200.0, + 'orderId' => 206054198198, + ], + ], + 'filePath' => '3000/attachment', + 'total' => 1000.0, + 'taxRecap' => [ + 'total' => 200.0, + 'taxes' => [ + [ + 'tax' => '15', + 'base' => 190.0, + 'total' => 200.0, + 'price' => 19.0, + ], + [ + 'tax' => '21', + 'base' => 190.0, + 'total' => 200.0, + 'price' => 19.0, + ], + [ + 'tax' => 'osvobozeno', + 'base' => 190.0, + 'total' => 200.0, + 'price' => 19.0, + ], + ], + ], + 'note' => '', + 'purchNoC' => '', + 'invoiceType' => 'SB', + 'invoiceIndicator' => 'I', + 'documentType' => 'invoice', + 'invoiceTypeTag' => 'SB', + ], +); + +Fixtures::add('offsetId', '0000003000-28032019'); +Fixtures::add( + 'offset', + [ + 'partner' => '3000', + 'documentNumber' => '0000003000-28032019', + 'createdAt' => new DateTime('2019-03-28 00:00:00'), + 'dueDate' => new DateTime('2018-12-07 00:00:00'), + 'currency' => 'CZK', + 'diffPrice' => -2550.91, + 'variableSymbol' => 3003190328, + 'supplier' => [ + 'name' => 'VIVANTIS a.s.', + 'registrationNumber' => '25977687', + 'taxIdentification' => 'CZ25977687', + 'vatNumber' => 'CZ25977687', + 'note' => '', + 'address' => [ + 'street' => 'Školní náměstí 14', + 'city' => 'Chrudim', + 'zip' => '537 01', + 'country' => 'CZ', + ], + ], + 'customer' => [ + 'name' => 'Internet Mall, a.s.', + 'registrationNumber' => '26204967', + 'taxIdentification' => 'CZ26204967', + 'vatNumber' => 'CZ26204967', + 'note' => '', + 'address' => [ + 'street' => 'U garáží 1611/1', + 'city' => 'Praha 7 - Holešovice', + 'zip' => '170 00', + 'country' => 'CZ', + ], + ], + 'invoices' => [ + [ + 'id' => 3003000034, + 'created' => new DateTime('2018-11-23 00:00:00'), + 'dueDate' => new DateTime('2018-12-07 00:00:00'), + 'sumPrice' => 2973.91, + 'offsetPrice' => 423, + 'remainPrice' => 2550.91, + 'currency' => 'CZK', + 'purchNoC' => '', + 'note' => '', + 'invoiceNumber' => '', + ], + ], + 'orders' => [ + [ + 'id' => 10016693501, + 'created' => new DateTime('2019-03-27 00:00:00'), + 'dueDate' => new DateTime('2019-04-10 00:00:00'), + 'sumPrice' => -423, + 'offsetPrice' => -423, + 'remainPrice' => 0, + 'currency' => 'CZK', + ], + ], + 'attachment' => [ + 'filename' => 'MP_offset_0000003000-28032019.PDF', + 'mime' => 'application/pdf', + ], + ], +); + +Fixtures::add('orderId', 232342423102); + +Fixtures::add( + 'order', + [ + 'id' => 232342423102, + 'purchase_id' => 2323424231, + 'external_order_id' => 0, + 'currency' => 'CZK', + 'delivery_price' => 99.1, + 'cod_price' => 10, + 'cod' => 111, + 'discount' => 100.1, + 'payment_type' => 'A', + 'delivery_method' => '123', + 'delivery_method_id' => 123, + 'branches' => [ + 'overridden' => false, + ], + 'tracking_number' => 'ABRA0001', + 'ship_date' => new DateTime('2018-02-01 00:00:00'), + 'delivery_date' => new DateTime('2018-02-01 00:00:00'), + 'address' => [ + 'customer_id' => 1000929311, + 'name' => 'Josef Novák', + 'company' => 'U garážá 1', + 'phone' => '+420701000001', + 'email' => 'info@mall.cz', + 'street' => 'U garážá 1', + 'city' => 'Praha', + 'zip' => '17001', + 'country' => 'CZ', + ], + 'confirmed' => true, + 'status' => new OrderStatusEnum('shipping'), + 'items' => [ + [ + 'id' => '845841AB', + 'article_id' => 100000738462, + 'quantity' => 1, + 'price' => 1000.1, + 'vat' => 21, + 'commission' => 12.1, + 'title' => '', + 'serial_numbers' => [], + ], + ], + 'test' => false, + 'mdp' => false, + 'ready_to_return' => true, + 'shipped' => new DateTime('2020-01-09 12:08:56'), + 'open' => new DateTime('2020-01-09 13:33:49'), + 'lost' => new DateTime('2019-08-01 10:41:13'), + 'returned' => new DateTime('2019-04-29 15:25:09'), + 'delivered' => new DateTime('2019-04-29 15:24:52'), + 'shipping' => new DateTime('2021-03-16 12:49:28'), + 'ulozenka_status_history' => [], + ], +); + +Fixtures::add( + 'article-media', + new MediaIterator( + new Media('https://i.cdn.nrholding.net/21749465', true), + new Media('https://i.cdn.nrholding.net/21749466', false, null, true, false), + ), +); + +Fixtures::add( + 'article-availability', + new Availability(ArticleStatusEnum::ACTIVE(), 1), +); + +// Returns function, to avoid issues with references and cloning +Fixtures::add( + 'product', + function (): ProductRequest { + return new ProductRequest( + 'mpapi-client-test-product-id-not-set', + 'MPAPI client test product', + 'This is a test product created by MPAPI client tests', + 'This is a test product created by MPAPI client functional tests', + 'MP001', + 21, + 1, + ); + }, +); + +// Returns function, to avoid issues with references and cloning +Fixtures::add( + 'variant', + function (): VariantRequest { + return new VariantRequest( + 'mpapi-client-test-variant-id-not-set', + 'MPAPI client test variant', + 'This is a test variant created by MPAPI client tests', + 'This is a test variant created by MPAPI client functional tests', + 1, + 69, + Fixtures::get('article-media'), + new ParameterIterator(Parameter::create('MP_ATR_TEST1_CHAR', 'a', 'b', 'c')), + ); + } +); diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml new file mode 100644 index 0000000..16e5638 --- /dev/null +++ b/tests/unit.suite.yml @@ -0,0 +1,11 @@ +# Codeception Test Suite Configuration +# +# Suite for unit or integration tests. + +actor: UnitTester +namespace: MpApiClient\Tests\_support +modules: + enabled: + - Asserts + - MpApiClient\Tests\_support\Helper\Unit + step_decorators: ~ diff --git a/tests/unit/DataTypeUtilTest.php b/tests/unit/DataTypeUtilTest.php new file mode 100644 index 0000000..a381a4d --- /dev/null +++ b/tests/unit/DataTypeUtilTest.php @@ -0,0 +1,144 @@ + $input + * @param string|null $exceptionMsg + * @throws IncorrectDataTypeException + */ + public function testValidateStringArray(array $input, ?string $exceptionMsg): void + { + if ($exceptionMsg !== null) { + self::expectExceptionMessage($exceptionMsg); + } else { + self::expectNotToPerformAssertions(); + } + DataTypeUtil::validateStringArray($input); + } + + /** + * @dataProvider intArrayProvider + * + * @param array $input + * @param string|null $exceptionMsg + * @throws IncorrectDataTypeException + */ + public function testValidateIntArray(array $input, ?string $exceptionMsg): void + { + if ($exceptionMsg !== null) { + self::expectExceptionMessage($exceptionMsg); + } else { + self::expectNotToPerformAssertions(); + } + DataTypeUtil::validateIntArray($input); + } + + /** + * @dataProvider floatArrayProvider + * + * @param array $input + * @param string|null $exceptionMsg + * @throws IncorrectDataTypeException + */ + public function testValidateFloatArray(array $input, ?string $exceptionMsg): void + { + if ($exceptionMsg !== null) { + self::expectExceptionMessage($exceptionMsg); + } else { + self::expectNotToPerformAssertions(); + } + DataTypeUtil::validateFloatArray($input); + } + + /** + * @return array> + */ + public function stringArrayProvider(): array + { + return [ + 'empty data' => [ + [], + null, + ], + 'valid data' => [ + ['a', 'b', 'c'], + null, + ], + 'invalid data - int' => [ + ['a', 1, 'c'], + sprintf(self::INVALID_TYPE_ERR, 'string', 'integer'), + ], + 'invalid data - array' => [ + ['a', [], 'c'], + sprintf(self::INVALID_TYPE_ERR, 'string', 'array'), + ], + ]; + } + + /** + * @return array> + */ + public function intArrayProvider(): array + { + return [ + 'empty data' => [ + [], + null, + ], + 'valid data' => [ + [1, 2, 3], + null, + ], + 'invalid data - string' => [ + [1, 'a', 3], + sprintf(self::INVALID_TYPE_ERR, 'integer', 'string'), + ], + 'invalid data - array' => [ + [1, [], 3], + sprintf(self::INVALID_TYPE_ERR, 'integer', 'array'), + ], + ]; + } + + /** + * @return array> + */ + public function floatArrayProvider(): array + { + return [ + 'empty data' => [ + [], + null, + ], + 'valid data' => [ + [1.0, 2.1, 3.2], + null, + ], + 'invalid data - int' => [ + [1.0, 2, 3.2], + sprintf(self::INVALID_TYPE_ERR, 'float', 'integer'), + ], + 'invalid data - string' => [ + [1.0, 'a', 3.2], + sprintf(self::INVALID_TYPE_ERR, 'float', 'string'), + ], + 'invalid data - array' => [ + [1.0, [], 3.2], + sprintf(self::INVALID_TYPE_ERR, 'float', 'array'), + ], + ]; + } + +} diff --git a/tests/unit/FilterItemTest.php b/tests/unit/FilterItemTest.php new file mode 100644 index 0000000..111baa5 --- /dev/null +++ b/tests/unit/FilterItemTest.php @@ -0,0 +1,153 @@ +getColumn()); + self::assertEquals([$value], $item->getValues()); + self::assertEquals($operator, $item->getOperator()); + } + + public function testCreateInterval(): void + { + $column = 'column-name'; + $value1 = 'filter-value-1'; + $value2 = 'filter-value-2'; + + $item = FilterItem::createInterval($column, $value1, $value2); + self::assertEquals($column, $item->getColumn()); + self::assertEquals([$value1, $value2], $item->getValues()); + self::assertEquals(FilterOperatorEnum::BETWEEN(), $item->getOperator()); + } + + /** + * @dataProvider inclusionProvider + * + * @param string[] $values + */ + public function testCreateInclusion(string $column, array $values, bool $expectError): void + { + if ($expectError) { + self::expectError(); + } + + $item = FilterItem::createInclusion($column, ...$values); + self::assertEquals($column, $item->getColumn()); + self::assertEquals($values, $item->getValues()); + self::assertEquals(FilterOperatorEnum::IN(), $item->getOperator()); + } + + /** + * @dataProvider inclusionProvider + * + * @param string[] $values + */ + public function testCreateExclusion(string $column, array $values, bool $expectError): void + { + if ($expectError) { + self::expectError(); + } + + $item = FilterItem::createExclusion($column, ...$values); + self::assertEquals($column, $item->getColumn()); + self::assertEquals($values, $item->getValues()); + self::assertEquals(FilterOperatorEnum::NOT_IN(), $item->getOperator()); + } + + /** + * @return array> + */ + public function createProvider(): array + { + return [ + 'valid data - empty' => [ + 'column-name', + 'filter-value', + FilterOperatorEnum::EMPTY(), + null, + ], + 'valid data - equal' => [ + 'column-name', + 'filter-value', + FilterOperatorEnum::EQUAL(), + null, + ], + 'valid data - less than' => [ + 'column-name', + 'filter-value', + FilterOperatorEnum::LESS_THAN(), + null, + ], + 'invalid data - in' => [ + 'column-name', + 'filter-value', + FilterOperatorEnum::IN(), + sprintf('Unsupported operator [%s] used. Please use provided create methods.', FilterOperatorEnum::IN), + ], + 'invalid data - between' => [ + 'column-name', + 'filter-value', + FilterOperatorEnum::BETWEEN(), + sprintf('Unsupported operator [%s] used. Please use provided create methods.', FilterOperatorEnum::BETWEEN), + ], + ]; + } + + /** + * @return array> + */ + public function intervalProvider(): array + { + return [ + 'valid data' => [ + 'column-name', + 'filter-value-1', + 'filter-value-2', + ], + ]; + } + + /** + * @return array> + */ + public function inclusionProvider(): array + { + return [ + 'valid data - single' => [ + 'column-name', + ['filter-value'], + false, + ], + 'valid data - multiple' => [ + 'column-name', + ['filter-value-1', 'filter-value-2', 'filter-value-3'], + false, + ], + 'invalid data - wrong type' => [ + 'column-name', + ['filter-value-1', 'filter-value-2', 3, 4.5], + true, + ], + ]; + } + +} diff --git a/tests/unit/FilterTest.php b/tests/unit/FilterTest.php new file mode 100644 index 0000000..4917f26 --- /dev/null +++ b/tests/unit/FilterTest.php @@ -0,0 +1,98 @@ +addFilterItem($filterItem); + $filter->addFilterItem($filterInterval); + + $filter->addSortColumn('default-dir-sort'); + $filter->addSortColumn('asc-dir-sort', Filter::DIRECTION_DESC); + $filter->addSortColumn('asc-dir-sort', Filter::DIRECTION_ASC); // test rewrite of previous (incorrect) value + $filter->addSortColumn('desc-dir-sort', Filter::DIRECTION_DESC); + $filter->prependSortColumn('desc-dir-sort', Filter::DIRECTION_DESC); // test rewrite of previous value (moving it to the beginning) + + $filter->setLimit(100); + $filter->setOffset(500); + + // Check filter items + self::assertArrayHasKey('column-without-operator', $filter->getFilterItems()); + self::assertEquals($filterItem, $filter->getFilterItems()['column-without-operator']); + + self::assertArrayHasKey('column-between', $filter->getFilterItems()); + self::assertEquals($filterInterval, $filter->getFilterItems()['column-between']); + + // Check sort columns + self::assertArrayHasKey('default-dir-sort', $filter->getSortColumns()); + self::assertEquals(Filter::DIRECTION_ASC, $filter->getSortColumns()['default-dir-sort']); + + self::assertArrayHasKey('asc-dir-sort', $filter->getSortColumns()); + self::assertEquals(Filter::DIRECTION_ASC, $filter->getSortColumns()['asc-dir-sort']); + + self::assertArrayHasKey('desc-dir-sort', $filter->getSortColumns()); + self::assertEquals(Filter::DIRECTION_DESC, $filter->getSortColumns()['desc-dir-sort']); + + // Check limit and offset + self::assertEquals(100, $filter->getLimit()); + self::assertEquals(500, $filter->getOffset()); + + // Validate correct filter query with all items, sort and offset + self::assertEquals( + [ + 'column-without-operator' => 'value', + 'column-between' => 'bt:value1,value2', + '_sort' => 'desc-dir-sort:desc,default-dir-sort:asc,asc-dir-sort:asc', + 'page' => 6, + 'page_size' => 100, + ], + $filter->buildFilterQuery() + ); + + // Test removing items + $filter->removeFilterItem('column-between'); + self::assertArrayNotHasKey('column-between', $filter->getFilterItems()); + + $filter->removeSortColumn('asc-dir-sort'); + self::assertArrayNotHasKey('asc-dir-sort', $filter->getSortColumns()); + + // Test complete reset + $filter->resetSort(); + self::assertEmpty($filter->getSortColumns()); + + $filter->resetFilter(); + self::assertEmpty($filter->getFilterItems()); + + // Check correct query without filter or sort + self::assertEquals( + [ + 'page' => 6, + 'page_size' => 100, + ], + $filter->buildFilterQuery() + ); + } + + public function testSetLimit(): void + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(sprintf('Filter limit must be a positive number above zero, [%d] provided', 0)); + + $filter = new Filter(); + $filter->setLimit(0); + } + +} diff --git a/tests/unit/InputDataUtilTest.php b/tests/unit/InputDataUtilTest.php new file mode 100644 index 0000000..4e47bac --- /dev/null +++ b/tests/unit/InputDataUtilTest.php @@ -0,0 +1,99 @@ + $data + * @throws Exception + */ + public function testGetNullableDate(array $data): void + { + self::assertInstanceOf(DateTimeInterface::class, InputDataUtil::getNullableDate($data, 'date')); + self::assertInstanceOf(DateTimeInterface::class, InputDataUtil::getNullableDate($data, 'nested', 'date')); + self::assertNull(InputDataUtil::getNullableDate($data, 'non-existing-key')); + self::assertNull(InputDataUtil::getNullableDate($data, 'non', 'existing', 'key')); + } + + /** + * @dataProvider dataProvider + * + * @param array $data + * @throws Exception + */ + public function testGetNullableString(array $data): void + { + self::assertIsString(InputDataUtil::getNullableString($data, 'string')); + self::assertIsString(InputDataUtil::getNullableString($data, 'nested', 'string')); + self::assertNull(InputDataUtil::getNullableString($data, 'non-existing-key')); + self::assertNull(InputDataUtil::getNullableString($data, 'non', 'existing', 'key')); + } + + /** + * @dataProvider dataProvider + * + * @param array $data + * @throws Exception + */ + public function testGetNullableInt(array $data): void + { + self::assertIsInt(InputDataUtil::getNullableInt($data, 'int')); + self::assertIsInt(InputDataUtil::getNullableInt($data, 'nested', 'int')); + self::assertNull(InputDataUtil::getNullableInt($data, 'non-existing-key')); + self::assertNull(InputDataUtil::getNullableInt($data, 'non', 'existing', 'key')); + } + + /** + * @dataProvider dataProvider + * + * @param array $data + * @throws Exception + */ + public function testGetNullableFloat(array $data): void + { + self::assertIsFloat(InputDataUtil::getNullableFloat($data, 'float')); + self::assertIsFloat(InputDataUtil::getNullableFloat($data, 'nested', 'float')); + self::assertNull(InputDataUtil::getNullableFloat($data, 'non-existing-key')); + self::assertNull(InputDataUtil::getNullableFloat($data, 'non', 'existing', 'key')); + } + + /** + * @return array> + */ + public function dataProvider(): array + { + return [ + 'data' => [ + [ + 'a', + 'b', + 'c', + 'date' => '2001-01-31', + 'time' => '2001-01-31 06:00:00', + 'string' => 'a', + 'int' => 1, + 'float' => 1.0, + 'nested' => [ + 'a', + 'empty' => [], + 'date' => '2001-01-31', + 'time' => '2001-01-31 06:00:00', + 'string' => 'a', + 'int' => 1, + 'float' => 1.0, + ], + ], + ], + ]; + } + +} diff --git a/tests/unit/JsonSerializeEntityTraitTest.php b/tests/unit/JsonSerializeEntityTraitTest.php new file mode 100644 index 0000000..8413309 --- /dev/null +++ b/tests/unit/JsonSerializeEntityTraitTest.php @@ -0,0 +1,58 @@ +stringValue = 'string'; + $this->intValue = 123; + $this->floatValue = 123.45; + $this->serializableValue = new class implements JsonSerializable { + + public function jsonSerialize(): string + { + return 'serialized'; + } + + }; + $this->dateTimeValue = new DateTime('2001-01-31 06:00:00'); + } + +} + +final class JsonSerializeEntityTraitTest extends Unit +{ + + public function testJsonSerialize(): void + { + self::assertEquals( + [ + "stringValue" => "string", + "intValue" => 123, + "floatValue" => 123.45, + "serializableValue" => "serialized", + "dateTimeValue" => "2001-01-31 06:00:00", + ], + (new DummyEntityToTest())->jsonSerialize() + ); + } + +} diff --git a/tests/unit/StringEnumAbstractTest.php b/tests/unit/StringEnumAbstractTest.php new file mode 100644 index 0000000..894b663 --- /dev/null +++ b/tests/unit/StringEnumAbstractTest.php @@ -0,0 +1,144 @@ +getValue()); + + $second = StringEnumToTest::SECOND(); + self::assertInstanceOf(AbstractStringEnum::class, $second); + self::assertEquals(StringEnumToTest::SECOND(), (string) $second); + self::assertEquals(StringEnumToTest::SECOND, $second->getValue()); + + try { + /** @phpstan-ignore-next-line */ + StringEnumToTest::FOURTH(); + self::fail('static constructor should not allow invalid values'); + } catch (Error $e) { + self::assertEquals(sprintf('Call to undefined method %s::%s().', StringEnumToTest::class, 'FOURTH'), $e->getMessage()); + } + } + + public function testConstructor(): void + { + $first = new StringEnumToTest(StringEnumToTest::FIRST); + self::assertInstanceOf(AbstractStringEnum::class, $first); + self::assertEquals($first, StringEnumToTest::FIRST()); + + $second = new StringEnumToTest(StringEnumToTest::SECOND); + self::assertInstanceOf(AbstractStringEnum::class, $second); + self::assertEquals($second, StringEnumToTest::SECOND()); + + try { + new StringEnumToTest('fourth_value'); + self::fail('constructor should not allow invalid values'); + } catch (InvalidArgumentException $e) { + self::assertEquals(sprintf(self::INVALID_VALUE_MSG, 'fourth_value', implode(', ', StringEnumToTest::TYPES)), $e->getMessage()); + } + } + + public function testToString(): void + { + self::assertEquals(StringEnumToTest::FIRST, (string) StringEnumToTest::FIRST()); + self::assertEquals(StringEnumToTest::SECOND, (string) StringEnumToTest::SECOND()); + } + + public function testGetValue(): void + { + self::assertEquals(StringEnumToTest::FIRST, StringEnumToTest::FIRST()->getValue()); + self::assertEquals(StringEnumToTest::SECOND, StringEnumToTest::SECOND()->getValue()); + } + + public function testJsonSerialize(): void + { + self::assertEquals(sprintf('"%s"', StringEnumToTest::FIRST), json_encode(StringEnumToTest::FIRST())); + self::assertEquals(sprintf('"%s"', StringEnumToTest::SECOND), json_encode(StringEnumToTest::SECOND())); + } + + public function testIs(): void + { + $first = StringEnumToTest::FIRST(); + + self::assertTrue($first->equalsValue(StringEnumToTest::FIRST)); + self::assertTrue($first->equals(StringEnumToTest::FIRST())); + + self::assertFalse($first->equalsValue(StringEnumToTest::SECOND)); + self::assertFalse($first->equals(StringEnumToTest::SECOND())); + + try { + $first->equalsValue('fourth_value'); + self::fail('comparison should not allow invalid values'); + } catch (InvalidArgumentException $e) { + self::assertEquals(sprintf(self::INVALID_VALUE_MSG, 'fourth_value', implode(', ', StringEnumToTest::TYPES)), $e->getMessage()); + } + + try { + $first->equalsOneOfValues('fourth_value', 'fifth_value'); + self::fail('comparison should not allow invalid values'); + } catch (InvalidArgumentException $e) { + self::assertEquals( + sprintf(self::INVALID_VALUE_MSG, implode(', ', ['fourth_value', 'fifth_value']), implode(', ', StringEnumToTest::TYPES)), + $e->getMessage() + ); + } + } + + public function testIsOneOf(): void + { + $first = StringEnumToTest::FIRST(); + + self::assertTrue($first->equalsOneOfValues(StringEnumToTest::FIRST, StringEnumToTest::SECOND)); + self::assertTrue($first->equalsOneOf(StringEnumToTest::FIRST(), StringEnumToTest::SECOND())); + + self::assertFalse($first->equalsOneOfValues(StringEnumToTest::SECOND, StringEnumToTest::THIRD)); + self::assertFalse($first->equalsOneOf(StringEnumToTest::SECOND(), StringEnumToTest::THIRD())); + + try { + $first->equalsOneOfValues('fourth_value', 'fifth_value'); + self::fail('comparison should not allow invalid values'); + } catch (InvalidArgumentException $e) { + self::assertEquals( + sprintf(self::INVALID_VALUE_MSG, implode(', ', ['fourth_value', 'fifth_value']), implode(', ', StringEnumToTest::TYPES)), + $e->getMessage() + ); + } + } + +}