diff --git a/composer.json b/composer.json index 4b7fd2c..d128fb6 100755 --- a/composer.json +++ b/composer.json @@ -8,8 +8,10 @@ "ext-json": "*", "guzzlehttp/guzzle": "^6.0", "phpstan/phpstan-deprecation-rules": "^1.1", - "symfony/http-kernel": "^6.0", - "symfony/serializer": "^6.0", + "symfony/http-kernel": "^6.1", + "symfony/property-info": "^6.1", + "symfony/serializer": "^6.1", + "symfony/serializer-pack": "^1.3", "webmozart/assert": "^1.11" }, "require-dev": { @@ -19,7 +21,7 @@ "phpstan/phpstan-webmozart-assert": "^1.2.4", "phpunit/phpunit": "^10.5", "roave/security-advisories": "dev-master", - "symfony/phpunit-bridge": "6.1.*" + "symfony/phpunit-bridge": "6.2.*" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index 1010ce3..991938d 100755 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,5 +9,11 @@ parameters: treatPhpDocTypesAsCertain: false paths: - %rootDir%/../../../src - excludePaths: - - tests/* \ No newline at end of file + ignoreErrors: + - + message: '#.*NodeDefinition::children.*#' + path: ./src/DependencyInjection + + - + message: '#.*Extension::processConfiguration.*#' + path: ./src/DependencyInjection \ No newline at end of file diff --git a/src/Client/Client.php b/src/Client/Client.php index 7abedb6..2f2dd4f 100644 --- a/src/Client/Client.php +++ b/src/Client/Client.php @@ -17,11 +17,12 @@ class Client private ClientInterface $client; public function __construct( + private readonly ConfigProvider $configProvider, ?ClientInterface $client = null, ) { $this->client = $client ?? new GuzzleClient( [ - 'base_uri' => ConfigProvider::BASE_URL, + 'base_uri' => $this->configProvider->baseUrl, ] ); } diff --git a/src/Command/FindPoints.php b/src/Command/FindPoints.php index b21634c..192cdec 100644 --- a/src/Command/FindPoints.php +++ b/src/Command/FindPoints.php @@ -17,8 +17,11 @@ class FindPoints extends AbstractCommand private Client $client; private Serializer $serializer; - public function __construct(Client $client, Serializer $serializer) - { + public function __construct( + private readonly ConfigProvider $configProvider, + Client $client, + Serializer $serializer, + ) { $this->client = $client; $this->serializer = $serializer; } @@ -27,7 +30,7 @@ public function findPoints(FindPointsRequest $request): FindPointsResponse { $httpRequest = new HttpRequest( $request->getMethod(), - new Uri(ConfigProvider::API_VERSION . $request->getRequestUrl()), + new Uri($this->configProvider->apiVersion . $request->getRequestUrl()), [ 'Content-type' => 'application/json', ], diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 15ca236..92e42f3 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -6,6 +6,12 @@ class ConfigProvider { - public const BASE_URL = 'https://api-shipx-pl.easypack24.net/'; - public const API_VERSION = 'v1'; + private const BASE_URL = 'https://api-shipx-pl.easypack24.net/'; + private const API_VERSION = 'v1'; + + public function __construct( + public readonly string $baseUrl = self::BASE_URL, + public readonly string $apiVersion = self::API_VERSION, + ) { + } } diff --git a/src/DependencyInjection/AnswearInpostExtension.php b/src/DependencyInjection/AnswearInpostExtension.php index c313e9d..421455c 100755 --- a/src/DependencyInjection/AnswearInpostExtension.php +++ b/src/DependencyInjection/AnswearInpostExtension.php @@ -4,13 +4,26 @@ namespace Answear\InpostBundle\DependencyInjection; +use Answear\InpostBundle\ConfigProvider; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -class AnswearInpostExtension extends Extension +class AnswearInpostExtension extends Extension implements PrependExtensionInterface { + private array $config; + + public function prepend(ContainerBuilder $container): void + { + $configs = $container->getExtensionConfig($this->getAlias()); + $this->setConfig($container, $configs); + } + + /** + * @throws \Exception + */ public function load(array $configs, ContainerBuilder $container): void { $loader = new YamlFileLoader( @@ -18,5 +31,23 @@ public function load(array $configs, ContainerBuilder $container): void new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.yaml'); + + $this->setConfig($container, $configs); + + $definition = $container->getDefinition(ConfigProvider::class); + $definition->setArguments([ + $this->config['baseUrl'], + $this->config['apiVersion'], + ]); + } + + private function setConfig(ContainerBuilder $container, array $configs): void + { + if (isset($this->config)) { + return; + } + + $configuration = $this->getConfiguration($configs, $container); + $this->config = $this->processConfiguration($configuration, $configs); } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..3cfd576 --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,24 @@ +getRootNode() + ->children() + ?->scalarNode('baseUrl')->defaultNull()->end() + ?->scalarNode('apiVersion')->defaultNull()->end() + ?->end(); + + return $treeBuilder; + } +} diff --git a/src/Enum/PointType.php b/src/Enum/PointType.php index a661c3a..eee0c3a 100644 --- a/src/Enum/PointType.php +++ b/src/Enum/PointType.php @@ -10,4 +10,5 @@ enum PointType: string case Pop = 'pop'; case ParcelLockerOnly = 'parcel_locker_only'; case ParcelLockerSuperpop = 'parcel_locker_superpop'; + case Pok = 'pok'; } diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index fcfaf85..be278ed 100755 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -4,6 +4,9 @@ services: autoconfigure: true public: false + Answear\InpostBundle\ConfigProvider: ~ + Answear\InpostBundle\Client: ~ + Answear\InpostBundle\: resource: '../../../src/{Command,Client}' diff --git a/src/Response/Struct/Item.php b/src/Response/Struct/Item.php index 0a9ab38..30a2c04 100644 --- a/src/Response/Struct/Item.php +++ b/src/Response/Struct/Item.php @@ -22,6 +22,7 @@ class Item public ?string $locationDescription2; public ?int $distance; public ?string $openingHours; + public ?ItemOperatingHours $operatingHoursExtended; public ?ItemAddress $address; public ?ItemAddressDetails $addressDetails; public ?string $phoneNumber; @@ -36,6 +37,7 @@ class Item /** @var string[] */ public ?array $recommendedLowInterestBoxMachinesList; public ?bool $location247; + public ?bool $easyAccessZone; public static function fromArray(array $pointData): self { @@ -53,18 +55,22 @@ public static function fromArray(array $pointData): self $point->locationDescription2 = $pointData['location_description_2'] ?? null; $point->distance = $pointData['distance'] ?? null; $point->openingHours = $pointData['opening_hours'] ?? null; + $point->operatingHoursExtended = !empty($pointData['operating_hours_extended']) + ? ItemOperatingHours::fromArray($pointData['operating_hours_extended']) + : null; $point->address = !empty($pointData['address']) ? ItemAddress::fromArray($pointData['address']) : null; $point->addressDetails = !empty($pointData['address_details']) ? ItemAddressDetails::fromArray($pointData['address_details']) : null; $point->phoneNumber = $pointData['phone_number'] ?? null; $point->paymentPointDescr = $pointData['payment_point_descr'] ?? null; $point->functions = !empty($pointData['functions']) ? self::getFunctionsTypes($pointData['functions']) : null; $point->partnerId = $pointData['partner_id'] ?? null; - $point->isNext = $pointData['is_next'] ?? null; - $point->paymentAvailable = $pointData['payment_available'] ?? null; + $point->isNext = $pointData['is_next']; + $point->paymentAvailable = $pointData['payment_available']; $point->paymentType = $pointData['payment_type'] ?? null; $point->virtual = $pointData['virtual'] ?? null; $point->recommendedLowInterestBoxMachinesList = $pointData['recommended_low_interest_box_machines_list'] ?? null; - $point->location247 = $pointData['location_247'] ?? null; + $point->location247 = $pointData['location_247']; + $point->easyAccessZone = $pointData['easy_access_zone']; return $point; } diff --git a/src/Response/Struct/ItemOperatingHours.php b/src/Response/Struct/ItemOperatingHours.php new file mode 100644 index 0000000..55bda08 --- /dev/null +++ b/src/Response/Struct/ItemOperatingHours.php @@ -0,0 +1,37 @@ + $hours) { + if (isset($self->{strtolower($day)})) { + $self->{strtolower($day)} = $hours; + } + } + + return $self; + } +} diff --git a/tests/Integration/Command/FindPointsTest.php b/tests/Integration/Command/FindPointsTest.php index ca05742..74a9490 100644 --- a/tests/Integration/Command/FindPointsTest.php +++ b/tests/Integration/Command/FindPointsTest.php @@ -7,14 +7,15 @@ use Answear\InpostBundle\Client\Client; use Answear\InpostBundle\Client\Serializer; use Answear\InpostBundle\Command\FindPoints; +use Answear\InpostBundle\ConfigProvider; use Answear\InpostBundle\Enum\PointFunctionsType; use Answear\InpostBundle\Enum\PointType; use Answear\InpostBundle\Request\FindPointsRequest; -use Answear\InpostBundle\Response\FindPointsResponse; use Answear\InpostBundle\Response\Struct\Item; use Answear\InpostBundle\Response\Struct\ItemAddress; use Answear\InpostBundle\Response\Struct\ItemAddressDetails; use Answear\InpostBundle\Response\Struct\ItemLocation; +use Answear\InpostBundle\Response\Struct\ItemOperatingHours; use Answear\InpostBundle\Tests\MockGuzzleTrait; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; @@ -24,31 +25,30 @@ class FindPointsTest extends TestCase use MockGuzzleTrait; private Client $client; + private ConfigProvider $configProvider; + + private const POLAND = 'Poland'; + private const ITALY = 'Italy'; public function setUp(): void { parent::setUp(); - $this->client = new Client($this->setupGuzzleClient()); + $this->configProvider = new ConfigProvider(); + $this->client = new Client($this->configProvider, $this->setupGuzzleClient()); } /** * @test */ - public function successfulFindPoints(): void + public function successfulFindPointsPoland(): void { - $command = $this->getCommand(); - $this->mockGuzzleResponse(new Response(200, [], $this->getSuccessfulBody())); + $this->mockGuzzleResponse(new Response(200, [], $this->getSuccessfulBody(self::POLAND))); - $response = $command->findPoints(new FindPointsRequest()); + $response = $this->getCommand()->findPoints(new FindPointsRequest()); $this->assertCount(1, $response->getItems()); $this->assertSame(1, $response->getTotalItemsCount()); - $this->assertPoint($response); - } - - private function assertPoint(FindPointsResponse $response): void - { /** @var Item $point */ $point = $response->getItems()->get(0); @@ -66,7 +66,7 @@ private function assertPoint(FindPointsResponse $response): void $this->assertNull($point->locationDescription2); $this->assertNull($point->distance); $this->assertSame($point->openingHours, '24/7'); - $this->assertSame($point->openingHours, '24/7'); + $this->assertInstanceOf(ItemOperatingHours::class, $point->operatingHoursExtended); $this->assertInstanceOf(ItemAddress::class, $point->address); $this->assertSame($point->address->line1, 'Kościuszki 27'); $this->assertSame($point->address->line2, '21-412 Adamów'); @@ -89,14 +89,71 @@ private function assertPoint(FindPointsResponse $response): void $this->assertTrue($point->location247); } + /** + * @test + */ + public function successfulFindPointsItaly(): void + { + $this->mockGuzzleResponse(new Response(200, [], $this->getSuccessfulBody(self::ITALY))); + + $response = $this->getCommand()->findPoints(new FindPointsRequest()); + + $this->assertCount(1, $response->getItems()); + $this->assertSame(1, $response->getTotalItemsCount()); + /** @var Item $point */ + $point = $response->getItems()->get(0); + + $this->assertNotNull($point); + $this->assertSame($point->id, 'ITAAQ01570P'); + $this->assertSame($point->name, 'ITAAQ01570P'); + $this->assertSame($point->type, [PointType::Pok, PointType::Pop]); + $this->assertSame($point->status, 'Operating'); + $this->assertInstanceOf(ItemLocation::class, $point->location); + $this->assertSame($point->location->longitude, 13.47289); + $this->assertSame($point->location->latitude, 42.35751); + $this->assertSame($point->locationType, 'Indoor'); + $this->assertSame($point->locationDescription, 'presso Dottor Tech'); + $this->assertNull($point->locationDescription1); + $this->assertNull($point->locationDescription2); + $this->assertNull($point->distance); + $this->assertSame($point->openingHours, 'Lun-Ven: 10:00 - 13:00 - 16:00 - 19:00 '); + $this->assertInstanceOf(ItemOperatingHours::class, $point->operatingHoursExtended); + $this->assertInstanceOf(ItemAddress::class, $point->address); + $this->assertSame($point->address->line1, 'Via Ten. Antonio Rossi Tascione in Str. Vicinale di Paganica 7'); + $this->assertSame($point->address->line2, '67100 L\'Aquila'); + $this->assertInstanceOf(ItemAddressDetails::class, $point->addressDetails); + $this->assertSame($point->addressDetails->city, 'L\'Aquila'); + $this->assertSame($point->addressDetails->province, 'AQ'); + $this->assertSame($point->addressDetails->postCode, '67100'); + $this->assertSame($point->addressDetails->street, 'Via Ten. Antonio Rossi Tascione in Str. Vicinale di Paganica'); + $this->assertSame($point->addressDetails->buildingNumber, '7'); + $this->assertNull($point->addressDetails->flatNumber); + $this->assertNull($point->phoneNumber); + $this->assertSame($point->paymentPointDescr, ''); + $this->assertSame($point->functions, [ + PointFunctionsType::Parcel, + PointFunctionsType::ParcelCollect, + PointFunctionsType::ParcelReverseReturnSend, + PointFunctionsType::ParcelSend, + PointFunctionsType::StandardCourierSend, + ]); + $this->assertSame($point->partnerId, 1); + $this->assertFalse($point->isNext); + $this->assertTrue($point->paymentAvailable); + $this->assertSame($point->paymentType, ['3' => 'Payment by cash and card']); + $this->assertSame($point->virtual, '3'); + $this->assertNull($point->recommendedLowInterestBoxMachinesList); + $this->assertFalse($point->location247); + } + private function getCommand(): FindPoints { - return new FindPoints($this->client, new Serializer()); + return new FindPoints($this->configProvider, $this->client, new Serializer()); } - private function getSuccessfulBody(): string + private function getSuccessfulBody(string $country): string { - $response = file_get_contents(__DIR__ . '/data/exampleResponse.json'); + $response = file_get_contents(__DIR__ . sprintf('/data/exampleResponse_%s.json', $country)); if (false === $response) { throw new \RuntimeException('Cannot read example response file'); } diff --git a/tests/Integration/Command/data/exampleResponse.json b/tests/Integration/Command/data/exampleResponse.json deleted file mode 100644 index 4f3bb30..0000000 --- a/tests/Integration/Command/data/exampleResponse.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "href": "https://api-pl-points.easypack24.net/v1/points", - "count": 1, - "page": 1, - "per_page": 25, - "total_pages": 1, - "items": [ - { - "href": "https://api-pl-points.easypack24.net/v1/points/ADA01M", - "name": "ADA01M", - "type": [ - "parcel_locker" - ], - "status": "Operating", - "location": { - "longitude": 22.264049625, - "latitude": 51.73834066 - }, - "location_type": "Outdoor", - "location_date": null, - "location_description": "Przy sklepie Lewiatan", - "location_description_1": null, - "location_description_2": null, - "distance": null, - "opening_hours": "24/7", - "address": { - "line1": "Kościuszki 27", - "line2": "21-412 Adamów" - }, - "address_details": { - "city": "Adamów", - "province": "lubelskie", - "post_code": "21-412", - "street": "Kościuszki", - "building_number": "27", - "flat_number": null - }, - "phone_number": null, - "payment_point_descr": "Płatność internetowa PayByLink oraz Blik", - "functions": [ - "parcel_collect", - "parcel_send" - ], - "partner_id": 0, - "is_next": false, - "payment_available": true, - "payment_type": { - "0": "Payments are not supported" - }, - "virtual": "0", - "recommended_low_interest_box_machines_list": null, - "apm_doubled": null, - "location_247": true, - "operating_hours_extended": { - "customer": null - }, - "agency": "IPM4633224", - "image_url": "https://static.easypack24.net/points/pl/images/ADA01M.jpg" - } - ], - "meta": { - "href": "https://api-pl-points.easypack24.net/v1/points", - "count": 1, - "page": 1, - "per_page": 25, - "total_pages": 1 - } -} \ No newline at end of file diff --git a/tests/Integration/Command/data/exampleResponse_Italy.json b/tests/Integration/Command/data/exampleResponse_Italy.json new file mode 100644 index 0000000..b914af2 --- /dev/null +++ b/tests/Integration/Command/data/exampleResponse_Italy.json @@ -0,0 +1,126 @@ +{ + "href": "https://api-it-points.easypack24.net/v1/points", + "count": 1, + "page": 1, + "per_page": 25, + "total_pages": 1, + "items": [ + { + "href": "https://api-it-points.easypack24.net/v1/points/ITAAQ01570P", + "name": "ITAAQ01570P", + "type": [ + "pok", + "pop" + ], + "status": "Operating", + "location": { + "longitude": 13.47289, + "latitude": 42.35751 + }, + "location_type": "Indoor", + "location_date": null, + "location_description": "presso Dottor Tech", + "location_description_1": null, + "location_description_2": null, + "distance": null, + "opening_hours": "Lun-Ven: 10:00 - 13:00 - 16:00 - 19:00 ", + "address": { + "line1": "Via Ten. Antonio Rossi Tascione in Str. Vicinale di Paganica 7", + "line2": "67100 L'Aquila" + }, + "address_details": { + "city": "L'Aquila", + "province": "AQ", + "post_code": "67100", + "street": "Via Ten. Antonio Rossi Tascione in Str. Vicinale di Paganica", + "building_number": "7", + "flat_number": null + }, + "phone_number": null, + "payment_point_descr": "", + "functions": [ + "parcel", + "parcel_collect", + "parcel_reverse_return_send", + "parcel_send", + "standard_courier_send" + ], + "partner_id": 1, + "is_next": false, + "payment_available": true, + "payment_type": { + "3": "Payment by cash and card" + }, + "virtual": "3", + "recommended_low_interest_box_machines_list": null, + "apm_doubled": null, + "location_247": false, + "operating_hours_extended": { + "customer": { + "monday": [ + { + "start": 600, + "end": 780 + }, + { + "start": 960, + "end": 1140 + } + ], + "tuesday": [ + { + "start": 600, + "end": 780 + }, + { + "start": 960, + "end": 1140 + } + ], + "wednesday": [ + { + "start": 600, + "end": 780 + }, + { + "start": 960, + "end": 1140 + } + ], + "thursday": [ + { + "start": 600, + "end": 780 + }, + { + "start": 960, + "end": 1140 + } + ], + "friday": [ + { + "start": 600, + "end": 780 + }, + { + "start": 960, + "end": 1140 + } + ], + "saturday": [], + "sunday": [] + } + }, + "agency": "PES", + "image_url": "https://static.easypack24.net/points/it/images/ITAAQ01570P.jpg", + "easy_access_zone": false + } + ], + "meta": { + "href": "https://api-it-points.easypack24.net/v1/points", + "count": 6834, + "page": 1, + "per_page": 25, + "total_pages": 274 + } +} \ No newline at end of file diff --git a/tests/Integration/Command/data/exampleResponse_Poland.json b/tests/Integration/Command/data/exampleResponse_Poland.json new file mode 100644 index 0000000..003ab0e --- /dev/null +++ b/tests/Integration/Command/data/exampleResponse_Poland.json @@ -0,0 +1,68 @@ +{ + "href": "https://api-pl-points.easypack24.net/v1/points", + "count": 1, + "page": 1, + "per_page": 25, + "total_pages": 1, + "items": [ + { + "href": "https://api-pl-points.easypack24.net/v1/points/ADA01M", + "name": "ADA01M", + "type": [ + "parcel_locker" + ], + "status": "Operating", + "location": { + "longitude": 22.264049625, + "latitude": 51.73834066 + }, + "location_type": "Outdoor", + "location_date": null, + "location_description": "Przy sklepie Lewiatan", + "location_description_1": null, + "location_description_2": null, + "distance": null, + "opening_hours": "24/7", + "address": { + "line1": "Kościuszki 27", + "line2": "21-412 Adamów" + }, + "address_details": { + "city": "Adamów", + "province": "lubelskie", + "post_code": "21-412", + "street": "Kościuszki", + "building_number": "27", + "flat_number": null + }, + "phone_number": null, + "payment_point_descr": "Płatność internetowa PayByLink oraz Blik", + "functions": [ + "parcel_collect", + "parcel_send" + ], + "partner_id": 0, + "is_next": false, + "payment_available": true, + "payment_type": { + "0": "Payments are not supported" + }, + "virtual": "0", + "recommended_low_interest_box_machines_list": null, + "apm_doubled": null, + "location_247": true, + "operating_hours_extended": { + "customer": null + }, + "agency": "IPM4633224", + "image_url": "https://static.easypack24.net/points/pl/images/ADA01M.jpg" + } + ], + "meta": { + "href": "https://api-pl-points.easypack24.net/v1/points", + "count": 1, + "page": 1, + "per_page": 25, + "total_pages": 1 + } +} \ No newline at end of file