diff --git a/README.md b/README.md index aebc180..a77237d 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ use Answear\LuigisBoxBundle\ValueObject\SearchUrlBuilder; // ... $page = 3; -$urlBuilder = new SearchUrlBuilder('tracker-id', $page); +$urlBuilder = new SearchUrlBuilder($page); $urlBuilder ->setQuery('nice top') ->addFilter('type', 'product') @@ -168,7 +168,7 @@ $urlBuilder ->addPrefer('brand', 'Answear') ->setSort('size', 'asc'); -//the above code produces a url query like `size=10&tracker_id=tracker-id&page=3&q=nice+top&f%5B0%5D=type%3Aproduct&f%5B1%5D=category%3Atop&f%5B2%5D=brand%3AMedicine&f%5B3%5D=brand%3AAnswear&sort=size%3Aasc&prefer%5B0%5D=brand%3AAnswear` +//the above code produces a url query like `size=10&page=3&q=nice+top&f%5B0%5D=type%3Aproduct&f%5B1%5D=category%3Atop&f%5B2%5D=brand%3AMedicine&f%5B3%5D=brand%3AAnswear&sort=size%3Aasc&prefer%5B0%5D=brand%3AAnswear` /** @var \Answear\LuigisBoxBundle\Service\SearchRequest $request **/ $searchResponse = $request->search($urlBuilder); diff --git a/src/LuigisBoxBundle/Factory/SearchFactory.php b/src/LuigisBoxBundle/Factory/SearchFactory.php index ae590af..ccc0df8 100644 --- a/src/LuigisBoxBundle/Factory/SearchFactory.php +++ b/src/LuigisBoxBundle/Factory/SearchFactory.php @@ -8,6 +8,7 @@ use Answear\LuigisBoxBundle\ValueObject\SearchUrlBuilder; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Uri; +use Webmozart\Assert\Assert; class SearchFactory { @@ -25,9 +26,19 @@ public function __construct(ConfigProvider $configProvider) public function prepareRequest(SearchUrlBuilder $searchUrlBuilder): Request { + $urlQuery = $searchUrlBuilder->toUrlQuery(); + Assert::notEmpty($urlQuery); + return new Request( 'GET', - new Uri($this->configProvider->getHost() . self::ENDPOINT . '?' . $searchUrlBuilder->toUrlQuery()) + new Uri( + sprintf( + '%s?tracker_id=%s&%s', + $this->configProvider->getHost() . self::ENDPOINT, + $this->configProvider->getPublicKey(), + $urlQuery + ) + ) ); } } diff --git a/src/LuigisBoxBundle/Service/ConfigProvider.php b/src/LuigisBoxBundle/Service/ConfigProvider.php index 2e05357..dac4baf 100644 --- a/src/LuigisBoxBundle/Service/ConfigProvider.php +++ b/src/LuigisBoxBundle/Service/ConfigProvider.php @@ -65,6 +65,11 @@ public function getHost(): string return $this->host; } + public function getPublicKey(): string + { + return $this->publicKey; + } + public function getConnectionTimeout(): float { return $this->connectionTimeout; diff --git a/src/LuigisBoxBundle/ValueObject/SearchUrlBuilder.php b/src/LuigisBoxBundle/ValueObject/SearchUrlBuilder.php index 388bab3..571848c 100644 --- a/src/LuigisBoxBundle/ValueObject/SearchUrlBuilder.php +++ b/src/LuigisBoxBundle/ValueObject/SearchUrlBuilder.php @@ -14,6 +14,7 @@ class SearchUrlBuilder public const ARRAY_ITEM_SEPARATOR = ':'; public const LIST_SEPARATOR = ','; public const DEFAULT_SIZE = 10; + public const RANGE_SEPARATOR = '|'; private const AVAILABLE_ORDER_DIRECTIONS = ['asc', 'desc']; /** @@ -31,6 +32,11 @@ class SearchUrlBuilder */ private $filters; + /** + * @var array|null + */ + private $mustFilters; + /** * @var int */ @@ -41,11 +47,6 @@ class SearchUrlBuilder */ private $sort; - /** - * @var string - */ - private $trackerId; - /** * @var string|null */ @@ -86,9 +87,8 @@ class SearchUrlBuilder */ private $userId; - public function __construct(string $trackerId, int $page = 1) + public function __construct(int $page = 1) { - $this->trackerId = $trackerId; $this->page = $page; } @@ -125,6 +125,10 @@ public function addFilter(string $key, string $value): self public function setFilters(array $filters): self { + if (\count($this->filters ?? []) > 0) { + throw new \LogicException('You already have the filters set. Use resetFilters() method to clear them first.'); + } + $keys = array_keys($filters); Assert::allString($keys, 'All filters keys must be string.'); @@ -133,6 +137,47 @@ public function setFilters(array $filters): self return $this; } + public function addMustFilter(string $key, string $value): self + { + $this->mustFilters[$key] = $this->mustFilters[$key] ?? []; + if (\is_string($this->mustFilters[$key])) { + $this->mustFilters[$key] = [$this->mustFilters[$key]]; + } + $this->mustFilters[$key][] = $value; + + $this->mustFilters[$key] = array_unique($this->mustFilters[$key]); + + return $this; + } + + public function setMustFilters(array $filters): self + { + if (\count($this->mustFilters ?? []) > 0) { + throw new \LogicException('You already have the must filters set. Use resetMustFilters() method to clear them first.'); + } + + $keys = array_keys($filters); + Assert::allString($keys, 'All must filters keys must be string.'); + + $this->mustFilters = $filters; + + return $this; + } + + public function resetFilters(): self + { + $this->filters = null; + + return $this; + } + + public function resetMustFilters(): self + { + $this->mustFilters = null; + + return $this; + } + public function setSize(int $size): self { $this->size = $size; @@ -223,7 +268,6 @@ public function toUrlQuery(): string { $queryFields = [ 'size' => $this->size, - 'tracker_id' => $this->trackerId, 'page' => $this->page, ]; @@ -253,6 +297,20 @@ public function toUrlQuery(): string $queryFields['f'] = $filtersFields; } + if (null !== $this->mustFilters) { + $filtersFields = []; + foreach ($this->mustFilters as $key => $values) { + if (\is_array($values)) { + foreach ($values as $value) { + $filtersFields[] = $key . self::ARRAY_ITEM_SEPARATOR . $value; + } + } else { + $filtersFields[] = $key . self::ARRAY_ITEM_SEPARATOR . $values; + } + } + $queryFields['f_must'] = $filtersFields; + } + if (null !== $this->sort) { $queryFields['sort'] = $this->sort; } @@ -309,7 +367,7 @@ public function toUrlQuery(): string } } - return http_build_query($queryFields); + return preg_replace('/%5B(\d+)%5D=/', '%5B%5D=', http_build_query($queryFields)); } public function __toString(): string diff --git a/tests/DataProvider/SearchDataProvider.php b/tests/DataProvider/SearchDataProvider.php index cf7ee70..d22ecad 100644 --- a/tests/DataProvider/SearchDataProvider.php +++ b/tests/DataProvider/SearchDataProvider.php @@ -10,7 +10,7 @@ class SearchDataProvider { public static function provideSuccessObjects(): iterable { - $urlBuilder = new SearchUrlBuilder('111111-222222', 2); + $urlBuilder = new SearchUrlBuilder(2); $urlBuilder->addFilter('type', 'product'); $urlBuilder->setQuicksearchTypes(['category']); @@ -158,7 +158,7 @@ public static function provideSuccessObjects(): iterable ], ]; - $urlBuilder = new SearchUrlBuilder('111111-222222'); + $urlBuilder = new SearchUrlBuilder(); $urlBuilder->setQuery('fila'); yield [ @@ -198,7 +198,7 @@ public static function provideSuccessObjects(): iterable ], ]; - $urlBuilder = new SearchUrlBuilder('111111-222222'); + $urlBuilder = new SearchUrlBuilder(); $urlBuilder->setQuery('fila'); $urlBuilder->addFilter('price', '5|2'); diff --git a/tests/Unit/Factory/SearchFactoryTest.php b/tests/Unit/Factory/SearchFactoryTest.php index 9e1b093..5bf7bf3 100644 --- a/tests/Unit/Factory/SearchFactoryTest.php +++ b/tests/Unit/Factory/SearchFactoryTest.php @@ -23,7 +23,7 @@ public function prepareRequestSuccessfully(): void $this->assertSame('GET', $request->getMethod()); $this->assertSame('host/search', $request->getUri()->getPath()); - $this->assertSame('size=10&tracker_id=tracker-id&page=34&q=query-string', $request->getUri()->getQuery()); + $this->assertSame('tracker_id=key&size=10&page=34&q=query-string', $request->getUri()->getQuery()); $this->assertSame('', $request->getBody()->getContents()); } @@ -37,7 +37,7 @@ private function getFactory(): SearchFactory private function getBuilderUrl(): SearchUrlBuilder { - $builder = new SearchUrlBuilder('tracker-id', 34); + $builder = new SearchUrlBuilder(34); $builder->setQuery('query-string'); return $builder; diff --git a/tests/Unit/Service/SearchRequestTest.php b/tests/Unit/Service/SearchRequestTest.php index e1eabf5..3e8f977 100644 --- a/tests/Unit/Service/SearchRequestTest.php +++ b/tests/Unit/Service/SearchRequestTest.php @@ -44,7 +44,7 @@ public function searchWithErrors(): void { $this->expectException(BadRequestException::class); - $urlBuilder = new SearchUrlBuilder('111111-222222', 2); + $urlBuilder = new SearchUrlBuilder(2); $context = new Context(); $context->setGeoLocationField('geo_location'); $urlBuilder->setContext($context); diff --git a/tests/Unit/ValueObject/SearchUrlBuilderTest.php b/tests/Unit/ValueObject/SearchUrlBuilderTest.php index 7bf1b67..0337267 100644 --- a/tests/Unit/ValueObject/SearchUrlBuilderTest.php +++ b/tests/Unit/ValueObject/SearchUrlBuilderTest.php @@ -6,6 +6,7 @@ use Answear\LuigisBoxBundle\ValueObject\Search\Context; use Answear\LuigisBoxBundle\ValueObject\SearchUrlBuilder; +use function GuzzleHttp\Psr7\parse_query; use PHPUnit\Framework\TestCase; class SearchUrlBuilderTest extends TestCase @@ -15,34 +16,40 @@ class SearchUrlBuilderTest extends TestCase */ public function buildValidUrlTest(): void { - $searchBuilder = new SearchUrlBuilder('123-345'); - $this->assertSame('size=10&tracker_id=123-345&page=1', $searchBuilder->toUrlQuery()); + $query = [ + 'size' => '10', + 'page' => '1', + ]; - $searchBuilder = new SearchUrlBuilder('123-345', 3); - $this->assertSame('size=10&tracker_id=123-345&page=3', $searchBuilder->toUrlQuery()); + $searchBuilder = new SearchUrlBuilder(); + $this->assertOk($query, $searchBuilder); + + $searchBuilder = new SearchUrlBuilder(3); + $query['page'] = '3'; + $this->assertOk($query, $searchBuilder); $searchBuilder->setQuery('to search query'); - $this->assertSame('size=10&tracker_id=123-345&page=3&q=to+search+query', $searchBuilder->toUrlQuery()); + $query['q'] = 'to search query'; + $this->assertOk($query, $searchBuilder); $searchBuilder->setUserId('user-id'); $searchBuilder->enableQueryUnderstanding(); - $this->assertSame( - 'size=10&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id', - $searchBuilder->toUrlQuery() - ); + $query['qu'] = '1'; + $query['user_id'] = 'user-id'; + $this->assertOk($query, $searchBuilder); $searchBuilder->addFilter('category', 'Top & Top'); - $this->assertSame( - 'size=10&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=category%3ATop+%26+Top', - $searchBuilder->toUrlQuery() - ); + $query['f[]'] = 'category:Top & Top'; + $this->assertOk($query, $searchBuilder); $searchBuilder->addFilter('category', 'Jeans'); - $this->assertSame( - 'size=10&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=category%3ATop+%26+Top&f%5B1%5D=category%3AJeans', - $searchBuilder->toUrlQuery() - ); + $query['f[]'] = [ + 'category:Top & Top', + 'category:Jeans', + ]; + $this->assertOk($query, $searchBuilder); + $searchBuilder->resetFilters(); $searchBuilder->setFilters( [ 'brand' => 'Elo & Hot16', @@ -50,47 +57,59 @@ public function buildValidUrlTest(): void 'price' => '5|2', ] ); - $this->assertSame( - 'size=10&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2', - $searchBuilder->toUrlQuery() + $query['f[]'] = [ + 'brand:Elo & Hot16', + 'category:Top', + 'category:Jeans', + 'price:5|2', + ]; + $this->assertOk($query, $searchBuilder); + + $searchBuilder->addMustFilter('category', 'Hannah'); + $query['f_must[]'] = 'category:Hannah'; + $this->assertOk($query, $searchBuilder); + + $searchBuilder->resetMustFilters(); + $query['f_must[]'] = [ + 'brand:Brand16', + 'category:George', + 'category:Prince', + ]; + $searchBuilder->setMustFilters( + [ + 'brand' => 'Brand16', + 'category' => ['George', 'Prince'], + ] ); + $this->assertOk($query, $searchBuilder); $searchBuilder->setSize(13); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2', - $searchBuilder->toUrlQuery() - ); + $query['size'] = '13'; + $this->assertOk($query, $searchBuilder); $searchBuilder->setSort('price', 'asc'); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc', - $searchBuilder->toUrlQuery() - ); + $query['sort'] = 'price:asc'; + $this->assertOk($query, $searchBuilder); $searchBuilder->setQuicksearchTypes(['price', 'title']); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle', - $searchBuilder->toUrlQuery() - ); + $query['quicksearch_types'] = 'price,title'; + $this->assertOk($query, $searchBuilder); $searchBuilder->setFacets(['brand', 'attribute']); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute', - $searchBuilder->toUrlQuery() - ); + $query['facets'] = 'brand,attribute'; + $this->assertOk($query, $searchBuilder); $searchBuilder->setFixits(false); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute&use_fixits=0', - $searchBuilder->toUrlQuery() - ); + $query['use_fixits'] = '0'; + $this->assertOk($query, $searchBuilder); $searchBuilder->addPrefer('category', 'Gadgets'); $searchBuilder->addPrefer('category', 'Ona'); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute&use_fixits=0&prefer%5B0%5D=category%3AGadgets&prefer%5B1%5D=category%3AOna', - $searchBuilder->toUrlQuery() - ); + $query['prefer[]'] = [ + 'category:Gadgets', + 'category:Ona', + ]; + $this->assertOk($query, $searchBuilder); $searchBuilder->setPreferArray( [ @@ -98,16 +117,16 @@ public function buildValidUrlTest(): void 'type' => ['Products', 'Category'], ] ); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute&use_fixits=0&prefer%5B0%5D=category%3AGadgets&prefer%5B1%5D=type%3AProducts&prefer%5B2%5D=type%3ACategory', - $searchBuilder->toUrlQuery() - ); + $query['prefer[]'] = [ + 'category:Gadgets', + 'type:Products', + 'type:Category', + ]; + $this->assertOk($query, $searchBuilder); $searchBuilder->setHitFields(['brand', 'attribute']); - $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute&use_fixits=0&prefer%5B0%5D=category%3AGadgets&prefer%5B1%5D=type%3AProducts&prefer%5B2%5D=type%3ACategory&hit_fields=brand%2Cattribute', - $searchBuilder->toUrlQuery() - ); + $query['hit_fields'] = 'brand,attribute'; + $this->assertOk($query, $searchBuilder); $context = new Context(); $context->setGeoLocation(12.31, 24.271); @@ -116,9 +135,25 @@ public function buildValidUrlTest(): void $context->setBoostField('boost'); $context->setFreshnessField('freshness'); $searchBuilder->setContext($context); + $query['context[geo_location]'] = '12.31,24.271'; + $query['context[geo_location_field]'] = 'geolocation'; + $query['context[availability_field]'] = 'availability'; + $query['context[boost_field]'] = 'boost'; + $query['context[freshness_field]'] = 'freshness'; + $this->assertOk($query, $searchBuilder); + + //simply check url string $this->assertSame( - 'size=13&tracker_id=123-345&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B0%5D=brand%3AElo+%26+Hot16&f%5B1%5D=category%3ATop&f%5B2%5D=category%3AJeans&f%5B3%5D=price%3A5%7C2&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute&use_fixits=0&prefer%5B0%5D=category%3AGadgets&prefer%5B1%5D=type%3AProducts&prefer%5B2%5D=type%3ACategory&hit_fields=brand%2Cattribute&context%5Bgeo_location%5D=12.31%2C24.271&context%5Bgeo_location_field%5D=geolocation&context%5Bavailability_field%5D=availability&context%5Bboost_field%5D=boost&context%5Bfreshness_field%5D=freshness', + 'size=13&page=3&q=to+search+query&qu=1&user_id=user-id&f%5B%5D=brand%3AElo+%26+Hot16&f%5B%5D=category%3ATop&f%5B%5D=category%3AJeans&f%5B%5D=price%3A5%7C2&f_must%5B%5D=brand%3ABrand16&f_must%5B%5D=category%3AGeorge&f_must%5B%5D=category%3APrince&sort=price%3Aasc&quicksearch_types=price%2Ctitle&facets=brand%2Cattribute&use_fixits=0&prefer%5B%5D=category%3AGadgets&prefer%5B%5D=type%3AProducts&prefer%5B%5D=type%3ACategory&hit_fields=brand%2Cattribute&context%5Bgeo_location%5D=12.31%2C24.271&context%5Bgeo_location_field%5D=geolocation&context%5Bavailability_field%5D=availability&context%5Bboost_field%5D=boost&context%5Bfreshness_field%5D=freshness', $searchBuilder->toUrlQuery() ); } + + private function assertOk(array $query, SearchUrlBuilder $searchBuilder): void + { + $this->assertSame( + $query, + parse_query($searchBuilder->toUrlQuery()) + ); + } }