commit f1d0ecaa396bb230724d1e5224a234f6f3beb387 Author: Павел Date: Fri Sep 25 18:06:41 2020 +0300 initial diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e291365 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..906105c --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/vendor +/bin +composer.lock +composer.phar +coverage.xml +test-report.xml +phpunit.xml +.phpunit.result.cache +.idea +.DS_Store +.settings +.buildpath +.project +.swp +/nbproject diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3593c90 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: php + +cache: + directories: + - $HOME/.composer/cache + +php: + - '7.4' + +before_script: + - flags="-o" + - composer install $flags + +script: php ./vendor/phpunit/phpunit/phpunit -c phpunit.xml.dist + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..289e5e6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright © 2020 RetailDriver + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7d6c5d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# AliExpress TOP API client + +API client implementation for AliExpress TOP. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8b513b9 --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "retailcrm/aliexpress-top-client", + "description": "API client implementation for AliExpress TOP.", + "type": "library", + "keywords": ["API", "retailCRM", "REST"], + "homepage": "http://www.retailcrm.ru/", + "authors": [ + { + "name": "RetailDriver LLC", + "email": "integration@retailcrm.ru" + } + ], + "support": { + "email": "support@retailcrm.ru" + }, + "autoload": { + "psr-4": { "RetailCrm\\": "src/" } + }, + "require": { + "php": ">=7.4.0", + "ext-curl": "*", + "ext-json": "*", + "psr/http-client": "^1.0", + "symfony/serializer": "^5.1", + "symfony/validator": "^5.1", + "devanych/di-container": "^2.1", + "doctrine/annotations": "^1.10", + "doctrine/cache": "^1.10", + "symfony/property-access": "^5.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "phpmd/phpmd": "^2.9", + "squizlabs/php_codesniffer": "^3.5", + "guzzlehttp/guzzle": "^7.1" + }, + "license": "MIT" +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d9a8450 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,36 @@ + + + + + + + tests + + + + + + src + + + + + + + + \ No newline at end of file diff --git a/src/Component/Environment.php b/src/Component/Environment.php new file mode 100644 index 0000000..4a64920 --- /dev/null +++ b/src/Component/Environment.php @@ -0,0 +1,66 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +namespace RetailCrm\Component; + +use Psr\Container\ContainerInterface; +use RetailCrm\Factory\ClientFactory; +use RetailCrm\TopClient\Client; + +/** + * Class Environment + * + * @category Environment + * @package RetailCrm\Component + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class Environment +{ + public const PROD = 'PROD'; + public const DEV = 'DEV'; + public const TEST = 'TEST'; + + private ContainerInterface $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @return \Psr\Container\ContainerInterface + */ + public function getContainer(): ContainerInterface + { + return $this->container; + } + + /** + * @param string $serviceUrl + * + * @return \RetailCrm\TopClient\Client + * @throws \RetailCrm\Component\Exception\ValidationException + */ + public function createClient(string $serviceUrl): Client + { + $factory = $this->container->get(ClientFactory::class); + + if (!($factory instanceof ClientFactory)) { + throw new \RuntimeException('Invalid factory definition in the provided container'); + } + + return $factory->create($serviceUrl); + } +} diff --git a/src/Component/Exception/ValidationException.php b/src/Component/Exception/ValidationException.php new file mode 100644 index 0000000..e9accde --- /dev/null +++ b/src/Component/Exception/ValidationException.php @@ -0,0 +1,58 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +namespace RetailCrm\Component\Exception; + +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Throwable; + +/** + * Class ValidationException + * + * @category ValidationException + * @package RetailCrm\Component\Exception + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class ValidationException extends \Exception +{ + private ?ConstraintViolationListInterface $violations; + + /** + * ValidationException constructor. + * + * @param string $message + * @param \Symfony\Component\Validator\ConstraintViolationListInterface|null $violations + * @param int $code + * @param \Throwable|null $previous + */ + public function __construct( + $message = "", + ?ConstraintViolationListInterface $violations = null, + $code = 0, + Throwable $previous = null + ) { + parent::__construct($message, $code, $previous); + + $this->violations = $violations; + } + + /** + * @return \Symfony\Component\Validator\ConstraintViolationListInterface|null + */ + public function getViolations(): ?ConstraintViolationListInterface + { + return $this->violations; + } +} diff --git a/src/Factory/ClientFactory.php b/src/Factory/ClientFactory.php new file mode 100644 index 0000000..f03084e --- /dev/null +++ b/src/Factory/ClientFactory.php @@ -0,0 +1,55 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +namespace RetailCrm\Factory; + +use RetailCrm\Interfaces\HttpClientAwareInterface; +use RetailCrm\Interfaces\ValidatorAwareInterface; +use RetailCrm\TopClient\Client; +use RetailCrm\Traits\HttpClientAwareTrait; +use RetailCrm\Traits\ValidatorAwareTrait; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerAwareTrait; + +/** + * Class ClientFactory + * + * @category ClientFactory + * @package RetailCrm\Factory + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class ClientFactory implements HttpClientAwareInterface, SerializerAwareInterface, ValidatorAwareInterface +{ + use HttpClientAwareTrait; + use SerializerAwareTrait; + use ValidatorAwareTrait; + + /** + * @param string $serviceUrl + * + * @return \RetailCrm\TopClient\Client + * @throws \RetailCrm\Component\Exception\ValidationException + */ + public function create(string $serviceUrl): Client + { + $client = new Client($serviceUrl); + $client->setHttpClient($this->httpClient); + $client->setSerializer($this->serializer); + $client->setValidator($this->validator); + $client->validateSelf(); + + return $client; + } +} diff --git a/src/Factory/EnvironmentAwareFactoryInterface.php b/src/Factory/EnvironmentAwareFactoryInterface.php new file mode 100644 index 0000000..e03ca96 --- /dev/null +++ b/src/Factory/EnvironmentAwareFactoryInterface.php @@ -0,0 +1,42 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +namespace RetailCrm\Factory; + +use Psr\Http\Client\ClientInterface; +use RetailCrm\Component\Environment; + +/** + * Interface EnvironmentAwareFactoryInterface + * + * @category EnvironmentAwareFactoryInterface + * @package RetailCrm\Factory + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +interface EnvironmentAwareFactoryInterface +{ + /** + * @param string $environmentType + * + * @return \RetailCrm\Factory\EnvironmentAwareFactoryInterface + */ + public static function withEnv(string $environmentType = Environment::DEV): EnvironmentAwareFactoryInterface; + + /** + * @return \RetailCrm\Component\Environment + */ + public function create(ClientInterface $httpClient): Environment; +} diff --git a/src/Factory/EnvironmentFactory.php b/src/Factory/EnvironmentFactory.php new file mode 100644 index 0000000..05a1d16 --- /dev/null +++ b/src/Factory/EnvironmentFactory.php @@ -0,0 +1,116 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +namespace RetailCrm\Factory; + +use Devanych\Di\Container; +use Psr\Container\ContainerInterface; +use Psr\Http\Client\ClientInterface; +use RetailCrm\TopClient\Client; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerAwareTrait; +use Symfony\Component\Validator\Validation; +use RetailCrm\Component\Environment; +use Symfony\Component\Validator\Validator\TraceableValidator; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Class EnvironmentFactory + * + * @category EnvironmentFactory + * @package RetailCrm\Factory + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class EnvironmentFactory implements EnvironmentAwareFactoryInterface +{ + public string $env; + public ClientInterface $httpClient; + + /** + * @param string $environmentType + * + * @return \RetailCrm\Factory\EnvironmentAwareFactoryInterface + */ + public static function withEnv(string $environmentType = Environment::DEV): EnvironmentAwareFactoryInterface + { + $factory = new self(); + $factory->env = $environmentType; + + return $factory; + } + + /** + * @param \Psr\Http\Client\ClientInterface $httpClient + * + * @return \RetailCrm\Component\Environment + */ + public function create(ClientInterface $httpClient): Environment + { + $this->httpClient = $httpClient; + + $container = new Container(); + + switch ($this->env) { + case Environment::PROD: + $this->setProdServices($container); + break; + case Environment::DEV: + case Environment::TEST: + $this->setProdServices($container); + $this->setDevServices($container); + break; + default: + throw new \RuntimeException(sprintf('Invalid environment type: %s', $this->env)); + } + + return new Environment($container); + } + + /** + * @param Container $container + */ + protected function setProdServices(Container $container): void + { + $container->set('httpClient', $this->httpClient); + $container->set('validator', Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator()); + $container->set('serializer', new Serializer( + [new ObjectNormalizer()], + [new XmlEncoder(), new JsonEncoder()] + )); + $container->set(ClientFactory::class, function (ContainerInterface $container): ClientFactory { + $factory = new ClientFactory(); + $factory->setHttpClient($container->get('httpClient')); + $factory->setSerializer($container->get('serializer')); + $factory->setValidator($container->get('validator')); + + return $factory; + }); + } + + /** + * @param Container $container + */ + protected function setDevServices(Container $container): void + { + $validator = $container->get('validator'); + + if ($validator instanceof ValidatorInterface) { + $container->set('validator', new TraceableValidator($validator)); + } + } +} diff --git a/src/Interfaces/HttpClientAwareInterface.php b/src/Interfaces/HttpClientAwareInterface.php new file mode 100644 index 0000000..e7ae373 --- /dev/null +++ b/src/Interfaces/HttpClientAwareInterface.php @@ -0,0 +1,36 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +namespace RetailCrm\Interfaces; + +use Psr\Http\Client\ClientInterface; + +/** + * Interface HttpClientAwareInterface + * + * @category HttpClientAwareInterface + * @package RetailCrm\Interfaces + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +interface HttpClientAwareInterface +{ + /** + * @param \Psr\Http\Client\ClientInterface $httpClient + * + * @return mixed + */ + public function setHttpClient(ClientInterface $httpClient): void; +} diff --git a/src/Interfaces/ValidatorAwareInterface.php b/src/Interfaces/ValidatorAwareInterface.php new file mode 100644 index 0000000..312ab81 --- /dev/null +++ b/src/Interfaces/ValidatorAwareInterface.php @@ -0,0 +1,34 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +namespace RetailCrm\Interfaces; + +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Interface ValidatorAwareInterface + * + * @category ValidatorAwareInterface + * @package RetailCrm\Interfaces + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +interface ValidatorAwareInterface +{ + /** + * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator + */ + public function setValidator(ValidatorInterface $validator): void; +} diff --git a/src/TopClient/Client.php b/src/TopClient/Client.php new file mode 100644 index 0000000..122fe2c --- /dev/null +++ b/src/TopClient/Client.php @@ -0,0 +1,85 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +namespace RetailCrm\TopClient; + +use RetailCrm\Component\Exception\ValidationException; +use RetailCrm\Interfaces\HttpClientAwareInterface; +use RetailCrm\Interfaces\ValidatorAwareInterface; +use RetailCrm\Traits\HttpClientAwareTrait; +use RetailCrm\Traits\ValidatorAwareTrait; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerAwareTrait; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * Class Client + * + * @category Client + * @package RetailCrm\TopClient + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class Client implements SerializerAwareInterface, HttpClientAwareInterface, ValidatorAwareInterface +{ + use ValidatorAwareTrait; + use HttpClientAwareTrait; + use SerializerAwareTrait; + + public const OVERSEAS_ENDPOINT = 'https://api.taobao.com/router/rest'; + public const CHINESE_ENDPOINT = 'https://eco.taobao.com/router/rest'; + public const AVAILABLE_ENDPOINTS = [self::OVERSEAS_ENDPOINT, self::CHINESE_ENDPOINT]; + + /** + * @Assert\Url() + * @Assert\Choice(choices=Client::AVAILABLE_ENDPOINTS, message="Choose a valid endpoint.") + */ + protected string $serviceUrl; + + /** + * Client constructor. + * + * @param string serviceUrl + */ + public function __construct(string $serviceUrl) + { + $this->serviceUrl = $serviceUrl; + } + + /** + * @throws \RetailCrm\Component\Exception\ValidationException + */ + public function validateSelf(): void + { + $violations = $this->validator->validate($this); + + if ($violations->count()) { + throw new ValidationException("Invalid client data", $violations); + } + } + + /** + * @param mixed $item + * + * @throws \RetailCrm\Component\Exception\ValidationException + */ + private function validate($item): void + { + $violations = $this->validator->validate($item); + + if ($violations->count()) { + throw new ValidationException("Invalid data", $item); + } + } +} diff --git a/src/Traits/HttpClientAwareTrait.php b/src/Traits/HttpClientAwareTrait.php new file mode 100644 index 0000000..5cc450b --- /dev/null +++ b/src/Traits/HttpClientAwareTrait.php @@ -0,0 +1,43 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +namespace RetailCrm\Traits; + +use Psr\Http\Client\ClientInterface; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * Trait HttpClientAwareTrait + * + * @category HttpClientAwareTrait + * @package RetailCrm\Traits + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +trait HttpClientAwareTrait +{ + /** + * @Assert\NotNull(message="HTTP client should be provided") + */ + protected ClientInterface $httpClient; + + /** + * @param \Psr\Http\Client\ClientInterface $httpClient + */ + public function setHttpClient(ClientInterface $httpClient): void + { + $this->httpClient = $httpClient; + } +} diff --git a/src/Traits/ValidatorAwareTrait.php b/src/Traits/ValidatorAwareTrait.php new file mode 100644 index 0000000..8abe8b4 --- /dev/null +++ b/src/Traits/ValidatorAwareTrait.php @@ -0,0 +1,43 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + +namespace RetailCrm\Traits; + +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Trait ValidatorAwareTrait + * + * @category ValidatorAwareTrait + * @package RetailCrm\Traits + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +trait ValidatorAwareTrait +{ + /** + * @Assert\NotNull(message="Validator should be provided") + */ + protected ValidatorInterface $validator; + + /** + * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator + */ + public function setValidator(ValidatorInterface $validator): void + { + $this->validator = $validator; + } +} diff --git a/tests/Component/EnvironmentTest.php b/tests/Component/EnvironmentTest.php new file mode 100644 index 0000000..58f586c --- /dev/null +++ b/tests/Component/EnvironmentTest.php @@ -0,0 +1,40 @@ + + * @license MIT + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ +namespace Component; + +use GuzzleHttp\Client as HttpClient; +use PHPUnit\Framework\TestCase; +use RetailCrm\Factory\EnvironmentFactory; +use RetailCrm\TopClient\Client; + +/** + * Class EnvironmentTest + * + * @category EnvironmentTest + * @package Component + * @author RetailDriver LLC + * @license MIT + * @link http://retailcrm.ru + * @see https://help.retailcrm.ru + */ +class EnvironmentTest extends TestCase +{ + public function testCreateClient() + { + $client = EnvironmentFactory::withEnv() + ->create(new HttpClient()) + ->createClient(Client::OVERSEAS_ENDPOINT); + + self::assertInstanceOf(Client::class, $client); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..26ce5dc --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,10 @@ +add('RetailCrm\\Test', __DIR__);