From 7d295d748ab360af756f1e510f1d1f9f0edc6199 Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Tue, 13 Apr 2021 18:23:09 +0200 Subject: [PATCH 1/8] Client skeleton --- .../AnswearDpdPlPickupServicesExtension.php | 12 ++++ src/DependencyInjection/Configuration.php | 1 - src/Exception/ServiceException.php | 9 +++ src/Service/ConfigProvider.php | 34 ++++++++++ src/Service/PUDOList.php | 65 +++++++++++++++++++ src/ValueObject/PUDO.php | 9 +++ 6 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/Exception/ServiceException.php create mode 100644 src/Service/ConfigProvider.php create mode 100644 src/Service/PUDOList.php create mode 100644 src/ValueObject/PUDO.php diff --git a/src/DependencyInjection/AnswearDpdPlPickupServicesExtension.php b/src/DependencyInjection/AnswearDpdPlPickupServicesExtension.php index 5cf8e3d..c21fb35 100644 --- a/src/DependencyInjection/AnswearDpdPlPickupServicesExtension.php +++ b/src/DependencyInjection/AnswearDpdPlPickupServicesExtension.php @@ -19,5 +19,17 @@ public function load(array $configs, ContainerBuilder $container): void new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.yaml'); + + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + $definition = $container->getDefinition(ConfigProvider::class); + $definition->setArguments( + [ + $config['url'], + $config['key'], + $config['requestTimeout'], + ] + ); } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index c2e9038..a0378ae 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -21,7 +21,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('url')->defaultValue(self::API_URL)->end() ->scalarNode('key')->cannotBeEmpty()->end() - ->floatNode('connectionTimeout')->defaultValue(self::CONNECTION_TIMEOUT)->end() ->floatNode('requestTimeout')->defaultValue(self::REQUEST_TIMEOUT)->end() ->end(); diff --git a/src/Exception/ServiceException.php b/src/Exception/ServiceException.php new file mode 100644 index 0000000..792bf8d --- /dev/null +++ b/src/Exception/ServiceException.php @@ -0,0 +1,9 @@ +url = $url; + $this->key = $key; + $this->requestTimeout = $requestTimeout; + } + + public function getUrl(): string + { + return $this->url; + } + + public function getKey(): string + { + return $this->key; + } + + public function getRequestTimeout(): float + { + return $this->requestTimeout; + } +} diff --git a/src/Service/PUDOList.php b/src/Service/PUDOList.php new file mode 100644 index 0000000..0232234 --- /dev/null +++ b/src/Service/PUDOList.php @@ -0,0 +1,65 @@ +configProvider = $configProvider; + $this->client = $client ?? new Client( + [ + 'base_uri' => $configProvider->getUrl(), + 'http_errors' => false, + 'timeout' => $configProvider->getRequestTimeout(), + ] + ); + } + + /** + * @return PUDO[] + * + * @throws ServiceException + */ + public function byAddress(string $zipCode, string $city, ?string $street = null): array + { + // @todo + } + + /** + * @return PUDO[] + * + * @throws ServiceException + */ + public function byCountry(string $countryCode): array + { + // @todo + } + + /** + * @throws ServiceException + */ + public function byId(string $id): PUDO + { + // @todo + } + + /** + * @return PUDO[] + * + * @throws ServiceException + */ + public function byLatLng(float $latitude, float $longitude, float $distance): array + { + // @todo + } +} diff --git a/src/ValueObject/PUDO.php b/src/ValueObject/PUDO.php new file mode 100644 index 0000000..5cf9957 --- /dev/null +++ b/src/ValueObject/PUDO.php @@ -0,0 +1,9 @@ + Date: Tue, 13 Apr 2021 18:47:58 +0200 Subject: [PATCH 2/8] Model PUDO objects --- src/Enum/Day.php | 53 ++++++++++++++++++++++++ src/Enum/Service.php | 65 ++++++++++++++++++++++++++++++ src/Enum/Type.php | 29 +++++++++++++ src/Service/PUDOList.php | 3 +- src/ValueObject/AdditionalInfo.php | 17 ++++++++ src/ValueObject/Address.php | 21 ++++++++++ src/ValueObject/Coordinates.php | 18 +++++++++ src/ValueObject/HolidayDates.php | 17 ++++++++ src/ValueObject/OpeningTime.php | 21 ++++++++++ src/ValueObject/PUDO.php | 17 ++++++++ 10 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 src/Enum/Day.php create mode 100644 src/Enum/Service.php create mode 100644 src/Enum/Type.php create mode 100644 src/ValueObject/AdditionalInfo.php create mode 100644 src/ValueObject/Address.php create mode 100644 src/ValueObject/Coordinates.php create mode 100644 src/ValueObject/HolidayDates.php create mode 100644 src/ValueObject/OpeningTime.php diff --git a/src/Enum/Day.php b/src/Enum/Day.php new file mode 100644 index 0000000..8c6b6c2 --- /dev/null +++ b/src/Enum/Day.php @@ -0,0 +1,53 @@ +address1, $this->address2, $this->address3)); + } +} diff --git a/src/ValueObject/Coordinates.php b/src/ValueObject/Coordinates.php new file mode 100644 index 0000000..986efc1 --- /dev/null +++ b/src/ValueObject/Coordinates.php @@ -0,0 +1,18 @@ +latitude = $latitude; + $this->longitude = $longitude; + } +} diff --git a/src/ValueObject/HolidayDates.php b/src/ValueObject/HolidayDates.php new file mode 100644 index 0000000..c6eee77 --- /dev/null +++ b/src/ValueObject/HolidayDates.php @@ -0,0 +1,17 @@ +from = $from; + $this->to = $to; + } +} diff --git a/src/ValueObject/OpeningTime.php b/src/ValueObject/OpeningTime.php new file mode 100644 index 0000000..2d04697 --- /dev/null +++ b/src/ValueObject/OpeningTime.php @@ -0,0 +1,21 @@ +day = $day; + $this->from = $from; + $this->to = $to; + } +} diff --git a/src/ValueObject/PUDO.php b/src/ValueObject/PUDO.php index 5cf9957..b01e9ce 100644 --- a/src/ValueObject/PUDO.php +++ b/src/ValueObject/PUDO.php @@ -4,6 +4,23 @@ namespace Answear\DpdPlPickupServicesBundle\ValueObject; +use Answear\DpdPlPickupServicesBundle\Enum\Type; + class PUDO { + public string $id; + public bool $active; + public Type $type; + public string $language; + public Address $address; + public Coordinates $coordinates; + public AdditionalInfo $additionalInfo; + /** + * @var OpeningTime[] + */ + public array $open = []; + /** + * @var HolidayDates[] + */ + public array $holidays = []; } From 2bf33a77984e7b14727d7e630ac9ccd861065fa6 Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Wed, 14 Apr 2021 18:52:19 +0200 Subject: [PATCH 3/8] Transform data from XML --- composer.json | 2 +- src/Service/PUDOFactory.php | 91 +++++++++++++++++++++++++++++++ src/ValueObject/Coordinates.php | 5 +- src/ValueObject/PUDO.php | 5 +- src/ValueObject/Week.php | 36 ++++++++++++ tests/Service/PUDOFactoryTest.php | 66 ++++++++++++++++++++++ tests/ValueObject/WeekTest.php | 61 +++++++++++++++++++++ tests/fixtures/full_pudo.xml | 61 +++++++++++++++++++++ 8 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 src/Service/PUDOFactory.php create mode 100644 src/ValueObject/Week.php create mode 100644 tests/Service/PUDOFactoryTest.php create mode 100644 tests/ValueObject/WeekTest.php create mode 100644 tests/fixtures/full_pudo.xml diff --git a/composer.json b/composer.json index ef9688c..d83c9b5 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "require": { "php": "^7.4 || ^8.0", - "ext-xml": "*", + "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.0", "marc-mabe/php-enum": "^4.3", "symfony/http-kernel": "^4.4 || ^5.0", diff --git a/src/Service/PUDOFactory.php b/src/Service/PUDOFactory.php new file mode 100644 index 0000000..8a0e248 --- /dev/null +++ b/src/Service/PUDOFactory.php @@ -0,0 +1,91 @@ +active = 'true' === (string) $xml['active']; + $pudo->id = (string) $xml->PUDO_ID; + $pudo->type = Type::byValue((int) $xml->PUDO_TYPE); + $pudo->language = (string) $xml->LANGUAGE; + $pudo->address = $this->createAddress($xml); + $pudo->coordinates = new Coordinates((float) $xml->LATITUDE, (float) $xml->LONGITUDE); + $pudo->additionalInfo = $this->createAdditionalInfo($xml); + $pudo->opened = $this->createOpeningTimes($xml); + $pudo->holidays = $this->createHolidays($xml); + + return $pudo; + } + + private function createAddress(\SimpleXMLElement $xml): Address + { + $address = new Address(); + $address->address1 = (string) $xml->ADDRESS1; + $address->address2 = (string) $xml->ADDRESS2; + $address->address3 = (string) $xml->ADDRESS3; + $address->locationHint = (string) $xml->LOCATION_HINT; + $address->zipCode = (string) $xml->ZIPCODE; + $address->city = (string) $xml->CITY; + $address->country = (string) $xml->COUNTRY; + + return $address; + } + + private function createAdditionalInfo(\SimpleXMLElement $xml): AdditionalInfo + { + $info = new AdditionalInfo(); + foreach (explode(';', (string) $xml->SERVICE_PUDO) as $service) { + $info->services[] = Service::byValue($service); + } + $info->wheelchairAccessible = 'true' === (string) $xml->HANDICAPE; + $info->parking = 'true' === (string) $xml->PARKING; + + return $info; + } + + private function createOpeningTimes(\SimpleXMLElement $xml): Week + { + $openings = []; + foreach ($xml->OPENING_HOURS_ITEMS->OPENING_HOURS_ITEM as $opening) { + $openings[] = new OpeningTime( + Day::byValue((string) $opening->DAY_ID), + (string) $opening->START_TM, + (string) $opening->END_TM + ); + } + + return new Week(...$openings); + } + + /** + * @return HolidayDates[] + */ + private function createHolidays(\SimpleXMLElement $xml): array + { + $dates = []; + foreach ($xml->HOLIDAY_ITEMS->HOLIDAY_ITEM as $holiday) { + $dates[] = new HolidayDates( + \DateTimeImmutable::createFromFormat('d/m/Y', (string) $holiday->START_TM)->setTime(0, 0, 0), + \DateTimeImmutable::createFromFormat('d/m/Y', (string) $holiday->END_TM)->setTime(23, 59, 59) + ); + } + + return $dates; + } +} diff --git a/src/ValueObject/Coordinates.php b/src/ValueObject/Coordinates.php index 986efc1..42b57b4 100644 --- a/src/ValueObject/Coordinates.php +++ b/src/ValueObject/Coordinates.php @@ -4,6 +4,8 @@ namespace Answear\DpdPlPickupServicesBundle\ValueObject; +use Webmozart\Assert\Assert; + class Coordinates { public float $latitude; @@ -11,7 +13,8 @@ class Coordinates public function __construct(float $latitude, float $longitude) { - // @todo validation + Assert::range($latitude, -90, 90); + Assert::range($longitude, -180, 180); $this->latitude = $latitude; $this->longitude = $longitude; } diff --git a/src/ValueObject/PUDO.php b/src/ValueObject/PUDO.php index b01e9ce..57e4585 100644 --- a/src/ValueObject/PUDO.php +++ b/src/ValueObject/PUDO.php @@ -15,10 +15,7 @@ class PUDO public Address $address; public Coordinates $coordinates; public AdditionalInfo $additionalInfo; - /** - * @var OpeningTime[] - */ - public array $open = []; + public Week $opened; /** * @var HolidayDates[] */ diff --git a/src/ValueObject/Week.php b/src/ValueObject/Week.php new file mode 100644 index 0000000..c6bcad3 --- /dev/null +++ b/src/ValueObject/Week.php @@ -0,0 +1,36 @@ +openingTimes, $opening->day->getValue()); + $this->openingTimes[$opening->day->getValue()] = $opening; + } + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + foreach (Day::getEnumerators() as $day) { + yield $day => $this->openingTimes[$day->getValue()] ?? null; + } + } + + public function isOpened(Day $day): bool + { + return isset($this->openingTimes[$day->getValue()]); + } +} diff --git a/tests/Service/PUDOFactoryTest.php b/tests/Service/PUDOFactoryTest.php new file mode 100644 index 0000000..304126f --- /dev/null +++ b/tests/Service/PUDOFactoryTest.php @@ -0,0 +1,66 @@ +factory = new PUDOFactory(); + } + + /** + * @test + */ + public function dataIsCorrect(): void + { + $xml = simplexml_load_file(__DIR__ . '/../fixtures/full_pudo.xml'); + $pudo = $this->factory->fromXmlElement($xml->PUDO_ITEMS[0]->PUDO_ITEM); + + self::assertSame('PL15625', $pudo->id); + self::assertTrue($pudo->active); + self::assertTrue(Type::standard()->is($pudo->type)); + self::assertSame('PL', $pudo->language); + self::assertSame('Na Szaniec 21 box 4', $pudo->address->address1); + self::assertSame('ADDRESS2', $pudo->address->address2); + self::assertSame('ADDRESS3', $pudo->address->address3); + self::assertSame('Sklep Zoologiczny Akita : 885558055', $pudo->address->locationHint); + self::assertSame('31564', $pudo->address->zipCode); + self::assertSame('Kraków', $pudo->address->city); + self::assertSame('POL', $pudo->address->country); + self::assertEquals(new Coordinates(50.05874, 19.97894), $pudo->coordinates); + self::assertCount(3, $pudo->additionalInfo->services); + self::assertTrue($pudo->additionalInfo->parking); + self::assertFalse($pudo->additionalInfo->wheelchairAccessible); + foreach ($pudo->opened as $day => $opening) { + switch (true) { + case Day::sunday()->is($day): + self::assertNull($opening); + break; + case Day::saturday()->is($day): + self::assertSame('10:00', $opening->from); + self::assertSame('13:00', $opening->to); + break; + default: + self::assertSame('11:00', $opening->from); + self::assertSame('17:00', $opening->to); + break; + } + } + self::assertCount(1, $pudo->holidays); + self::assertEquals(new \DateTimeImmutable('06.04.2021 00:00:00'), $pudo->holidays[0]->from); + self::assertEquals(new \DateTimeImmutable('18.04.2021 23:59:59'), $pudo->holidays[0]->to); + } +} diff --git a/tests/ValueObject/WeekTest.php b/tests/ValueObject/WeekTest.php new file mode 100644 index 0000000..e83229d --- /dev/null +++ b/tests/ValueObject/WeekTest.php @@ -0,0 +1,61 @@ + $opening) { + self::assertTrue($expected[$i++]->is($day)); + } + } + + /** + * @test + */ + public function duplicatedDayResultsInException(): void + { + $this->expectException(\InvalidArgumentException::class); + + new Week( + new OpeningTime(Day::monday(), '10:00', '11:00'), + new OpeningTime(Day::monday(), '10:00', '11:00'), + ); + } + + /** + * @test + */ + public function isOpened(): void + { + $week = new Week(new OpeningTime(Day::monday(), '10:00', '11:00')); + self::assertTrue($week->isOpened(Day::monday())); + self::assertFalse($week->isOpened(Day::tuesday())); + } +} diff --git a/tests/fixtures/full_pudo.xml b/tests/fixtures/full_pudo.xml new file mode 100644 index 0000000..084b5e9 --- /dev/null +++ b/tests/fixtures/full_pudo.xml @@ -0,0 +1,61 @@ + + + + + PL15625 + 1 + 100 + 100;200;201 + PL + Na Szaniec 21 box 4 + ADDRESS2 + ADDRESS3 + Sklep Zoologiczny Akita : 885558055 + 31564 + Kraków + POL + 19.97894 + 50.05874 + true + false + + + 1 + 11:00 + 17:00 + + + 2 + 11:00 + 17:00 + + + 3 + 11:00 + 17:00 + + + 4 + 11:00 + 17:00 + + + 5 + 11:00 + 17:00 + + + 6 + 10:00 + 13:00 + + + + + 06/04/2021 + 18/04/2021 + + + + + \ No newline at end of file From e5561fea90955d7b53dfdc544d29d0388d111489 Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Thu, 15 Apr 2021 11:26:06 +0200 Subject: [PATCH 4/8] Improve naming --- src/Enum/Type.php | 12 ++++++------ src/Service/PUDOFactory.php | 6 +++--- src/ValueObject/PUDO.php | 2 +- src/ValueObject/{Week.php => WeekStoreHours.php} | 12 ++++++------ .../{WeekTest.php => WeekStoreHoursTest.php} | 12 ++++++------ 5 files changed, 22 insertions(+), 22 deletions(-) rename src/ValueObject/{Week.php => WeekStoreHours.php} (62%) rename tests/ValueObject/{WeekTest.php => WeekStoreHoursTest.php} (79%) diff --git a/src/Enum/Type.php b/src/Enum/Type.php index dcf3a99..b5c3d66 100644 --- a/src/Enum/Type.php +++ b/src/Enum/Type.php @@ -9,21 +9,21 @@ class Type extends Enum { public const STANDARD = 100; - public const NETWORK = 200; - public const FULL_TIME = 300; + public const CHAIN = 200; + public const DPD = 300; public static function standard(): self { return self::get(self::STANDARD); } - public static function network(): self + public static function chain(): self { - return self::get(self::NETWORK); + return self::get(self::CHAIN); } - public static function fullTime(): self + public static function dpd(): self { - return self::get(self::FULL_TIME); + return self::get(self::DPD); } } diff --git a/src/Service/PUDOFactory.php b/src/Service/PUDOFactory.php index 8a0e248..3131c66 100644 --- a/src/Service/PUDOFactory.php +++ b/src/Service/PUDOFactory.php @@ -13,7 +13,7 @@ use Answear\DpdPlPickupServicesBundle\ValueObject\HolidayDates; use Answear\DpdPlPickupServicesBundle\ValueObject\OpeningTime; use Answear\DpdPlPickupServicesBundle\ValueObject\PUDO; -use Answear\DpdPlPickupServicesBundle\ValueObject\Week; +use Answear\DpdPlPickupServicesBundle\ValueObject\WeekStoreHours; class PUDOFactory { @@ -59,7 +59,7 @@ private function createAdditionalInfo(\SimpleXMLElement $xml): AdditionalInfo return $info; } - private function createOpeningTimes(\SimpleXMLElement $xml): Week + private function createOpeningTimes(\SimpleXMLElement $xml): WeekStoreHours { $openings = []; foreach ($xml->OPENING_HOURS_ITEMS->OPENING_HOURS_ITEM as $opening) { @@ -70,7 +70,7 @@ private function createOpeningTimes(\SimpleXMLElement $xml): Week ); } - return new Week(...$openings); + return new WeekStoreHours(...$openings); } /** diff --git a/src/ValueObject/PUDO.php b/src/ValueObject/PUDO.php index 57e4585..564dca1 100644 --- a/src/ValueObject/PUDO.php +++ b/src/ValueObject/PUDO.php @@ -15,7 +15,7 @@ class PUDO public Address $address; public Coordinates $coordinates; public AdditionalInfo $additionalInfo; - public Week $opened; + public WeekStoreHours $opened; /** * @var HolidayDates[] */ diff --git a/src/ValueObject/Week.php b/src/ValueObject/WeekStoreHours.php similarity index 62% rename from src/ValueObject/Week.php rename to src/ValueObject/WeekStoreHours.php index c6bcad3..f84df90 100644 --- a/src/ValueObject/Week.php +++ b/src/ValueObject/WeekStoreHours.php @@ -7,15 +7,15 @@ use Answear\DpdPlPickupServicesBundle\Enum\Day; use Webmozart\Assert\Assert; -class Week implements \IteratorAggregate +class WeekStoreHours implements \IteratorAggregate { - private array $openingTimes = []; + private array $openingHours = []; public function __construct(OpeningTime ...$openingTimes) { foreach ($openingTimes as $opening) { - Assert::keyNotExists($this->openingTimes, $opening->day->getValue()); - $this->openingTimes[$opening->day->getValue()] = $opening; + Assert::keyNotExists($this->openingHours, $opening->day->getValue()); + $this->openingHours[$opening->day->getValue()] = $opening; } } @@ -25,12 +25,12 @@ public function __construct(OpeningTime ...$openingTimes) public function getIterator(): \Traversable { foreach (Day::getEnumerators() as $day) { - yield $day => $this->openingTimes[$day->getValue()] ?? null; + yield $day => $this->openingHours[$day->getValue()] ?? null; } } public function isOpened(Day $day): bool { - return isset($this->openingTimes[$day->getValue()]); + return isset($this->openingHours[$day->getValue()]); } } diff --git a/tests/ValueObject/WeekTest.php b/tests/ValueObject/WeekStoreHoursTest.php similarity index 79% rename from tests/ValueObject/WeekTest.php rename to tests/ValueObject/WeekStoreHoursTest.php index e83229d..52a3636 100644 --- a/tests/ValueObject/WeekTest.php +++ b/tests/ValueObject/WeekStoreHoursTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace ValueObject; +namespace Answear\DpdPlPickupServicesBundle\Tests\ValueObject; use Answear\DpdPlPickupServicesBundle\Enum\Day; use Answear\DpdPlPickupServicesBundle\ValueObject\OpeningTime; -use Answear\DpdPlPickupServicesBundle\ValueObject\Week; +use Answear\DpdPlPickupServicesBundle\ValueObject\WeekStoreHours; use PHPUnit\Framework\TestCase; -class WeekTest extends TestCase +class WeekStoreHoursTest extends TestCase { /** * @test @@ -25,7 +25,7 @@ public function daysAreInCorrectOrder(): void Day::saturday(), Day::sunday(), ]; - $week = new Week( + $week = new WeekStoreHours( new OpeningTime(Day::sunday(), '10:00', '11:00'), new OpeningTime(Day::wednesday(), '10:00', '11:00'), ); @@ -43,7 +43,7 @@ public function duplicatedDayResultsInException(): void { $this->expectException(\InvalidArgumentException::class); - new Week( + new WeekStoreHours( new OpeningTime(Day::monday(), '10:00', '11:00'), new OpeningTime(Day::monday(), '10:00', '11:00'), ); @@ -54,7 +54,7 @@ public function duplicatedDayResultsInException(): void */ public function isOpened(): void { - $week = new Week(new OpeningTime(Day::monday(), '10:00', '11:00')); + $week = new WeekStoreHours(new OpeningTime(Day::monday(), '10:00', '11:00')); self::assertTrue($week->isOpened(Day::monday())); self::assertFalse($week->isOpened(Day::tuesday())); } From 455948e73f666e962dbd42135a11bb2c2daa6fb4 Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Thu, 15 Apr 2021 16:37:01 +0200 Subject: [PATCH 5/8] Data fetching --- src/DependencyInjection/Configuration.php | 3 +- src/Exception/MalformedResponseException.php | 21 ++ src/Exception/ServiceException.php | 2 +- src/Resources/config/services.yaml | 2 + src/Service/ConfigProvider.php | 2 +- src/Service/PUDOList.php | 75 +++++- tests/Service/PUDOListTest.php | 231 +++++++++++++++++++ tests/fixtures/auth_error.xml | 6 + tests/fixtures/no_items.xml | 5 + tests/fixtures/three_results.xml | 142 ++++++++++++ 10 files changed, 475 insertions(+), 14 deletions(-) create mode 100644 src/Exception/MalformedResponseException.php create mode 100644 tests/Service/PUDOListTest.php create mode 100644 tests/fixtures/auth_error.xml create mode 100644 tests/fixtures/no_items.xml create mode 100644 tests/fixtures/three_results.xml diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index a0378ae..3b5e200 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -9,8 +9,7 @@ class Configuration implements ConfigurationInterface { - private const API_URL = 'https://mypudo.dpd.com.pl/api/'; - private const CONNECTION_TIMEOUT = 10.0; + public const API_URL = 'https://mypudo.dpd.com.pl/api/pudo/'; private const REQUEST_TIMEOUT = 10.0; public function getConfigTreeBuilder(): TreeBuilder diff --git a/src/Exception/MalformedResponseException.php b/src/Exception/MalformedResponseException.php new file mode 100644 index 0000000..306f856 --- /dev/null +++ b/src/Exception/MalformedResponseException.php @@ -0,0 +1,21 @@ +response = $response; + } + + public function getResponse(): string + { + return $this->response; + } +} diff --git a/src/Exception/ServiceException.php b/src/Exception/ServiceException.php index 792bf8d..c5606f5 100644 --- a/src/Exception/ServiceException.php +++ b/src/Exception/ServiceException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Answear\DpdPlPickupServicesBundle\Service; +namespace Answear\DpdPlPickupServicesBundle\Exception; class ServiceException extends \RuntimeException { diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 97e0378..e1f9ccb 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -4,3 +4,5 @@ services: autoconfigure: true public: false + Answear\DpdPlPickupServicesBundle\: + resource: '../../{Service}' diff --git a/src/Service/ConfigProvider.php b/src/Service/ConfigProvider.php index 98e9879..630a928 100644 --- a/src/Service/ConfigProvider.php +++ b/src/Service/ConfigProvider.php @@ -12,7 +12,7 @@ class ConfigProvider public function __construct(string $url, string $key, float $requestTimeout) { - $this->url = $url; + $this->url = rtrim($url, '/') . '/'; $this->key = $key; $this->requestTimeout = $requestTimeout; } diff --git a/src/Service/PUDOList.php b/src/Service/PUDOList.php index 7367d42..dd315e7 100644 --- a/src/Service/PUDOList.php +++ b/src/Service/PUDOList.php @@ -4,6 +4,8 @@ namespace Answear\DpdPlPickupServicesBundle\Service; +use Answear\DpdPlPickupServicesBundle\Exception\MalformedResponseException; +use Answear\DpdPlPickupServicesBundle\Exception\ServiceException; use Answear\DpdPlPickupServicesBundle\ValueObject\Coordinates; use Answear\DpdPlPickupServicesBundle\ValueObject\PUDO; use GuzzleHttp\Client; @@ -11,15 +13,20 @@ class PUDOList { + private PUDOFactory $PUDOFactory; private ConfigProvider $configProvider; private ClientInterface $client; - public function __construct(ConfigProvider $configProvider, ?ClientInterface $client = null) - { + public function __construct( + PUDOFactory $PUDOFactory, + ConfigProvider $configProvider, + ?ClientInterface $client = null + ) { + $this->PUDOFactory = $PUDOFactory; $this->configProvider = $configProvider; $this->client = $client ?? new Client( [ - 'base_uri' => $configProvider->getUrl(), + 'base_uri' => $configProvider->getUrl() . '/', 'http_errors' => false, 'timeout' => $configProvider->getRequestTimeout(), ] @@ -31,9 +38,19 @@ public function __construct(ConfigProvider $configProvider, ?ClientInterface $cl * * @throws ServiceException */ - public function byAddress(string $zipCode, string $city, ?string $street = null): array + public function byAddress(string $zipCode, string $city, ?string $address = null): array { - // @todo + $params = [ + 'requestID' => \uniqid(), + 'city' => $city, + 'zipCode' => $zipCode, + 'servicePudo_display' => 1, + ]; + if (null !== $address) { + $params['address'] = $address; + } + + return $this->request('list/byaddress', $params); } /** @@ -43,15 +60,17 @@ public function byAddress(string $zipCode, string $city, ?string $street = null) */ public function byCountry(string $countryCode): array { - // @todo + return $this->request('list/bycountry', ['countryCode' => $countryCode]); } /** * @throws ServiceException */ - public function byId(string $id): PUDO + public function byId(string $id): ?PUDO { - // @todo + $result = $this->request('details', ['pudoId' => $id]); + + return 1 === \count($result) ? \reset($result) : null; } /** @@ -59,8 +78,44 @@ public function byId(string $id): PUDO * * @throws ServiceException */ - public function byLatLng(Coordinates $coordinates, float $distance): array + public function byLatLng(Coordinates $coordinates, int $distance): array + { + return $this->request( + 'list/bylonglat', + [ + 'requestID' => \uniqid(), + 'latitude' => $coordinates->latitude, + 'longitude' => $coordinates->longitude, + 'max_distance_search' => $distance, + ] + ); + } + + /** + * @return PUDO[] + */ + private function request(string $endpoint, array $params): array { - // @todo + $params['key'] = $this->configProvider->getKey(); + + $response = $this->client->request('GET', $endpoint, ['query' => $params]); + if ($response->getBody()->isSeekable()) { + $response->getBody()->rewind(); + } + $responseText = $response->getBody()->getContents(); + $xml = @\simplexml_load_string($responseText); + if (false === $xml) { + throw new MalformedResponseException($responseText); + } + if ($xml->ERROR) { + throw new ServiceException((string) $xml->ERROR->VALUE, (int) $xml->ERROR['code']); + } + + $items = []; + foreach ($xml->PUDO_ITEMS->PUDO_ITEM as $pudo) { + $items[] = $this->PUDOFactory->fromXmlElement($pudo); + } + + return $items; } } diff --git a/tests/Service/PUDOListTest.php b/tests/Service/PUDOListTest.php new file mode 100644 index 0000000..6e703f8 --- /dev/null +++ b/tests/Service/PUDOListTest.php @@ -0,0 +1,231 @@ +guzzleHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->guzzleHandler); + + $this->guzzleHistory = []; + $history = Middleware::history($this->guzzleHistory); + $handlerStack->push($history); + + $this->PUDOList = new PUDOList( + new PUDOFactory(), + $this->createMock(ConfigProvider::class), + new Client( + [ + 'base_uri' => Configuration::API_URL, + 'handler' => $handlerStack, + 'http_errors' => false, + ] + ) + ); + } + + /** + * @test + */ + public function byAddress(): void + { + $this->guzzleHandler->append( + new Response( + 200, + [], + file_get_contents(__DIR__ . '/../fixtures/three_results.xml') + ) + ); + + $this->assertThreeResults($this->PUDOList->byAddress('31-564', 'Kraków', 'Aleja Pokoju 18')); + + /** @var Request $request */ + $request = $this->guzzleHistory[0]['request']; + self::assertSame('/api/pudo/list/byaddress', $request->getUri()->getPath()); + \parse_str($request->getUri()->getQuery(), $query); + self::assertArrayHasKey('requestID', $query); + self::assertArrayHasKey('key', $query); + self::assertSame('Kraków', $query['city']); + self::assertSame('31-564', $query['zipCode']); + self::assertSame('Aleja Pokoju 18', $query['address']); + self::assertSame('1', $query['servicePudo_display']); + } + + /** + * @test + */ + public function byCountry(): void + { + $this->guzzleHandler->append( + new Response( + 200, + [], + file_get_contents(__DIR__ . '/../fixtures/three_results.xml') + ) + ); + + $this->assertThreeResults($this->PUDOList->byCountry('POL')); + + /** @var Request $request */ + $request = $this->guzzleHistory[0]['request']; + self::assertSame('/api/pudo/list/bycountry', $request->getUri()->getPath()); + \parse_str($request->getUri()->getQuery(), $query); + self::assertArrayHasKey('key', $query); + self::assertSame('POL', $query['countryCode']); + } + + /** + * @test + */ + public function byCountryNoResults(): void + { + $this->guzzleHandler->append( + new Response( + 200, + [], + file_get_contents(__DIR__ . '/../fixtures/no_items.xml') + ) + ); + + self::assertCount(0, $this->PUDOList->byCountry('POL')); + } + + /** + * @test + */ + public function byId(): void + { + $this->guzzleHandler->append( + new Response( + 200, + [], + file_get_contents(__DIR__ . '/../fixtures/full_pudo.xml') + ) + ); + + $pudo = $this->PUDOList->byId('PL15625'); + self::assertInstanceOf(PUDO::class, $pudo); + self::assertSame('PL15625', $pudo->id); + + /** @var Request $request */ + $request = $this->guzzleHistory[0]['request']; + self::assertSame('/api/pudo/details', $request->getUri()->getPath()); + \parse_str($request->getUri()->getQuery(), $query); + self::assertArrayHasKey('key', $query); + self::assertSame('PL15625', $query['pudoId']); + } + + /** + * @test + */ + public function byIdNoResult(): void + { + $this->guzzleHandler->append( + new Response( + 200, + [], + file_get_contents(__DIR__ . '/../fixtures/no_items.xml') + ) + ); + + self::assertNull($this->PUDOList->byId('PL15625')); + } + + /** + * @test + */ + public function byLatLng(): void + { + $this->guzzleHandler->append( + new Response( + 200, + [], + file_get_contents(__DIR__ . '/../fixtures/three_results.xml') + ) + ); + + $this->assertThreeResults($this->PUDOList->byLatLng(new Coordinates(50.061389, 19.937222), 100)); + + /** @var Request $request */ + $request = $this->guzzleHistory[0]['request']; + self::assertSame('/api/pudo/list/bylonglat', $request->getUri()->getPath()); + \parse_str($request->getUri()->getQuery(), $query); + self::assertArrayHasKey('requestID', $query); + self::assertArrayHasKey('key', $query); + self::assertSame('50.061389', $query['latitude']); + self::assertSame('19.937222', $query['longitude']); + self::assertSame('100', $query['max_distance_search']); + } + + /** + * @test + */ + public function apiError(): void + { + $this->guzzleHandler->append( + new Response( + 401, + [], + file_get_contents(__DIR__ . '/../fixtures/auth_error.xml') + ) + ); + + $this->expectException(ServiceException::class); + $this->expectExceptionCode(314); + $this->expectExceptionMessage('Podana wartość key jest niepoprawna. Klucz nie istnieje lub jest nieaktywny.'); + + $this->PUDOList->byId('PL15625'); + } + + /** + * @test + */ + public function noXmlInResponse(): void + { + $this->guzzleHandler->append(new Response(500, [], 'Internal Server Error')); + + try { + $this->PUDOList->byId('PL15625'); + self::fail('An exception should have been thrown'); + } catch (MalformedResponseException $e) { + self::assertSame('Internal Server Error', $e->getResponse()); + } + } + + private function assertThreeResults(array $results): void + { + self::assertCount(3, $results); + $expected = ['PL11011', 'PL11016', 'PL11021']; + foreach ($results as $i => $result) { + self::assertInstanceOf(PUDO::class, $result); + self::assertSame($expected[$i], $result->id); + } + } +} diff --git a/tests/fixtures/auth_error.xml b/tests/fixtures/auth_error.xml new file mode 100644 index 0000000..2e66ac6 --- /dev/null +++ b/tests/fixtures/auth_error.xml @@ -0,0 +1,6 @@ + + + + Podana wartość key jest niepoprawna. Klucz nie istnieje lub jest nieaktywny. + + \ No newline at end of file diff --git a/tests/fixtures/no_items.xml b/tests/fixtures/no_items.xml new file mode 100644 index 0000000..4571e84 --- /dev/null +++ b/tests/fixtures/no_items.xml @@ -0,0 +1,5 @@ + + + 1 + + \ No newline at end of file diff --git a/tests/fixtures/three_results.xml b/tests/fixtures/three_results.xml new file mode 100644 index 0000000..b671340 --- /dev/null +++ b/tests/fixtures/three_results.xml @@ -0,0 +1,142 @@ + + + + + PL11011 + 1 + 100 + 100;101;200;201 + PL + Jerzego Samuela Bandtkiego 1 + Dpd Pickup : 534 -774 -300 + 30129 + Kraków + POL + 19.89947 + 50.07824 + true + false + + + 1 + 10:00 + 17:00 + + + 2 + 10:00 + 17:00 + + + 3 + 10:00 + 17:00 + + + 4 + 10:00 + 17:00 + + + 5 + 10:00 + 17:00 + + + + + + PL11016 + 1 + 100 + 100;200;201 + PL + Chopina 19A + Sklep Komputerowy Huzarcom : 668 380 849 + 55050 + Sobótka + POL + 16.74163 + 50.90165 + true + false + + + 1 + 09:00 + 17:00 + + + 2 + 09:00 + 17:00 + + + 3 + 09:00 + 17:00 + + + 4 + 09:00 + 17:00 + + + 5 + 09:00 + 17:00 + + + 6 + 09:00 + 14:00 + + + + + + PL11021 + 1 + 100 + 100;200;201 + PL + Piłsudskiego 9 + Punkt Xero : 694-800-550 + 31110 + Kraków + POL + 19.93095 + 50.0604 + true + false + + + 1 + 10:30 + 15:00 + + + 2 + 10:30 + 15:00 + + + 3 + 10:30 + 15:00 + + + 4 + 10:30 + 15:00 + + + 5 + 10:30 + 15:00 + + + + + + \ No newline at end of file From 93951dc907be454c7ad97dadf9aba16896ed16e7 Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Fri, 16 Apr 2021 16:38:21 +0200 Subject: [PATCH 6/8] Do not add spaces between ADDRESS fields --- src/ValueObject/Address.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ValueObject/Address.php b/src/ValueObject/Address.php index f5aa4fe..02168cf 100644 --- a/src/ValueObject/Address.php +++ b/src/ValueObject/Address.php @@ -16,6 +16,6 @@ class Address public function getFullAddress(): string { - return trim(sprintf('%s %s %s', $this->address1, $this->address2, $this->address3)); + return $this->address1 . $this->address2 . $this->address3; } } From 61e8b405d94d7ebe2e506f22a4b16d4e39fc546d Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Fri, 16 Apr 2021 16:46:40 +0200 Subject: [PATCH 7/8] Handle multiple opening times in same day --- src/ValueObject/WeekStoreHours.php | 14 +++++++++----- tests/Service/PUDOFactoryTest.php | 16 ++++++++++------ tests/ValueObject/WeekStoreHoursTest.php | 14 +------------- tests/fixtures/full_pudo.xml | 5 +++++ 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/ValueObject/WeekStoreHours.php b/src/ValueObject/WeekStoreHours.php index f84df90..32562cd 100644 --- a/src/ValueObject/WeekStoreHours.php +++ b/src/ValueObject/WeekStoreHours.php @@ -5,27 +5,31 @@ namespace Answear\DpdPlPickupServicesBundle\ValueObject; use Answear\DpdPlPickupServicesBundle\Enum\Day; -use Webmozart\Assert\Assert; class WeekStoreHours implements \IteratorAggregate { + /** + * @var OpeningTime[][] + */ private array $openingHours = []; public function __construct(OpeningTime ...$openingTimes) { foreach ($openingTimes as $opening) { - Assert::keyNotExists($this->openingHours, $opening->day->getValue()); - $this->openingHours[$opening->day->getValue()] = $opening; + if (!isset($this->openingHours[$opening->day->getValue()])) { + $this->openingHours[$opening->day->getValue()] = []; + } + $this->openingHours[$opening->day->getValue()][] = $opening; } } /** - * @return \Traversable + * @return \Traversable */ public function getIterator(): \Traversable { foreach (Day::getEnumerators() as $day) { - yield $day => $this->openingHours[$day->getValue()] ?? null; + yield $day => $this->openingHours[$day->getValue()] ?? []; } } diff --git a/tests/Service/PUDOFactoryTest.php b/tests/Service/PUDOFactoryTest.php index 304126f..72ce029 100644 --- a/tests/Service/PUDOFactoryTest.php +++ b/tests/Service/PUDOFactoryTest.php @@ -44,18 +44,22 @@ public function dataIsCorrect(): void self::assertCount(3, $pudo->additionalInfo->services); self::assertTrue($pudo->additionalInfo->parking); self::assertFalse($pudo->additionalInfo->wheelchairAccessible); - foreach ($pudo->opened as $day => $opening) { + foreach ($pudo->opened as $day => $openings) { switch (true) { case Day::sunday()->is($day): - self::assertNull($opening); + self::assertCount(0, $openings); break; case Day::saturday()->is($day): - self::assertSame('10:00', $opening->from); - self::assertSame('13:00', $opening->to); + self::assertCount(2, $openings); + self::assertSame('10:00', $openings[0]->from); + self::assertSame('13:00', $openings[0]->to); + self::assertSame('18:00', $openings[1]->from); + self::assertSame('20:00', $openings[1]->to); break; default: - self::assertSame('11:00', $opening->from); - self::assertSame('17:00', $opening->to); + self::assertCount(1, $openings); + self::assertSame('11:00', $openings[0]->from); + self::assertSame('17:00', $openings[0]->to); break; } } diff --git a/tests/ValueObject/WeekStoreHoursTest.php b/tests/ValueObject/WeekStoreHoursTest.php index 52a3636..54f1b70 100644 --- a/tests/ValueObject/WeekStoreHoursTest.php +++ b/tests/ValueObject/WeekStoreHoursTest.php @@ -27,6 +27,7 @@ public function daysAreInCorrectOrder(): void ]; $week = new WeekStoreHours( new OpeningTime(Day::sunday(), '10:00', '11:00'), + new OpeningTime(Day::sunday(), '12:00', '14:00'), new OpeningTime(Day::wednesday(), '10:00', '11:00'), ); @@ -36,19 +37,6 @@ public function daysAreInCorrectOrder(): void } } - /** - * @test - */ - public function duplicatedDayResultsInException(): void - { - $this->expectException(\InvalidArgumentException::class); - - new WeekStoreHours( - new OpeningTime(Day::monday(), '10:00', '11:00'), - new OpeningTime(Day::monday(), '10:00', '11:00'), - ); - } - /** * @test */ diff --git a/tests/fixtures/full_pudo.xml b/tests/fixtures/full_pudo.xml index 084b5e9..ad8f7e6 100644 --- a/tests/fixtures/full_pudo.xml +++ b/tests/fixtures/full_pudo.xml @@ -49,6 +49,11 @@ 10:00 13:00 + + 6 + 18:00 + 20:00 + From cfaa4df88b0b05db620232d33e4b741f01932f1e Mon Sep 17 00:00:00 2001 From: Maciej Malarz Date: Fri, 16 Apr 2021 16:53:49 +0200 Subject: [PATCH 8/8] Add unlisted PUDO types --- src/Enum/Type.php | 30 +++++++++++++++++++++++++++--- src/Service/PUDOFactory.php | 2 +- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Enum/Type.php b/src/Enum/Type.php index b5c3d66..bab2db4 100644 --- a/src/Enum/Type.php +++ b/src/Enum/Type.php @@ -8,9 +8,13 @@ class Type extends Enum { - public const STANDARD = 100; - public const CHAIN = 200; - public const DPD = 300; + public const STANDARD = '100'; + public const CHAIN = '200'; + public const DPD = '300'; + public const SWIP_BOX_1 = '400401'; + public const SWIP_BOX_2 = '400402'; + public const POINT_PACK = '500501'; + public const FOREIGN_LOCKER = '400'; public static function standard(): self { @@ -26,4 +30,24 @@ public static function dpd(): self { return self::get(self::DPD); } + + public static function swipBox1(): self + { + return self::get(self::SWIP_BOX_1); + } + + public static function swipBox2(): self + { + return self::get(self::SWIP_BOX_2); + } + + public static function pointPack(): self + { + return self::get(self::POINT_PACK); + } + + public static function foreignLocker(): self + { + return self::get(self::FOREIGN_LOCKER); + } } diff --git a/src/Service/PUDOFactory.php b/src/Service/PUDOFactory.php index 3131c66..c680fba 100644 --- a/src/Service/PUDOFactory.php +++ b/src/Service/PUDOFactory.php @@ -22,7 +22,7 @@ public function fromXmlElement(\SimpleXMLElement $xml): PUDO $pudo = new PUDO(); $pudo->active = 'true' === (string) $xml['active']; $pudo->id = (string) $xml->PUDO_ID; - $pudo->type = Type::byValue((int) $xml->PUDO_TYPE); + $pudo->type = Type::byValue((string) $xml->PUDO_TYPE); $pudo->language = (string) $xml->LANGUAGE; $pudo->address = $this->createAddress($xml); $pudo->coordinates = new Coordinates((float) $xml->LATITUDE, (float) $xml->LONGITUDE);