Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add simple product name and description search #564

Open
wants to merge 2 commits into
base: 1.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions doc/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,35 @@ paths:
schema:
$ref: "#/definitions/ProductDetails"

/products/by-phrase/{phrase}:
get:
tags:
- "products"
summary: "Show a product catalog."
description: "This endpoint will return a paginated list of products for given phrase."
operationId: "productCatalogByPhrase"
parameters:
- name: "phrase"
in: "path"
description: "Phrase to search for."
required: true
type: "string"
- name: "locale"
in: "query"
description: "Locale in which products should be shown."
required: false
type: "string"
- name: "includeDescription"
in: "query"
description: "Should the description included in search."
required: false
type: "boolean"
responses:
200:
CSchulz marked this conversation as resolved.
Show resolved Hide resolved
description: "Show a product with the given code."
schema:
$ref: "#/definitions/ProductDetails"

/products/by-slug/{slug}/reviews:
parameters:
- name: "slug"
Expand Down
59 changes: 59 additions & 0 deletions src/Controller/Product/ShowProductCatalogByPhraseAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Sylius\ShopApiPlugin\Controller\Product;

use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Channel\Context\ChannelNotFoundException;
use Sylius\ShopApiPlugin\Model\PaginatorDetails;
use Sylius\ShopApiPlugin\ViewRepository\Product\ProductCatalogViewRepositoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class ShowProductCatalogByPhraseAction
{
/** @var ViewHandlerInterface */
private $viewHandler;

/** @var ProductCatalogViewRepositoryInterface */
private $productCatalogViewRepository;

/** @var ChannelContextInterface */
private $channelContext;

public function __construct(
ViewHandlerInterface $viewHandler,
ProductCatalogViewRepositoryInterface $productLatestQuery,
ChannelContextInterface $channelContext
) {
$this->viewHandler = $viewHandler;
$this->productCatalogViewRepository = $productLatestQuery;
$this->channelContext = $channelContext;
}

public function __invoke(Request $request): Response
{
try {
$channel = $this->channelContext->getChannel();
$phrase = $request->attributes->get('phrase');
$queryParameters = $request->query->all();
$queryParameters['phrase'] = $phrase;

return $this->viewHandler->handle(View::create($this->productCatalogViewRepository->findByPhrase(
$phrase,
$channel->getCode(),
new PaginatorDetails($request->attributes->get('_route'), $queryParameters),
$request->query->getBoolean('includeDescription', false),
$request->query->get('locale')
), Response::HTTP_OK));
} catch (ChannelNotFoundException $exception) {
throw new NotFoundHttpException('Channel has not been found.');
} catch (\InvalidArgumentException $exception) {
throw new NotFoundHttpException($exception->getMessage());
}
}
}
4 changes: 4 additions & 0 deletions src/Resources/config/routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ sylius_shop_api_product_by_code:
resource: "@SyliusShopApiPlugin/Resources/config/routing/productByCode.yml"
prefix: /shop-api

sylius_shop_api_product_by_phrase:
resource: "@SyliusShopApiPlugin/Resources/config/routing/productByPhrase.yml"
prefix: /shop-api

sylius_shop_api_taxon:
resource: "@SyliusShopApiPlugin/Resources/config/routing/taxon.yml"
prefix: /shop-api
Expand Down
7 changes: 7 additions & 0 deletions src/Resources/config/routing/productByPhrase.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
sylius_shop_api_product_show_by_phrase:
path: /products/by-phrase/{phrase}
methods: [GET]
defaults:
_controller: sylius.shop_api_plugin.controller.product.show_product_catalog_by_phrase_action
requirements:
phrase: .+
8 changes: 8 additions & 0 deletions src/Resources/config/services/actions/product.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
<argument type="service" id="sylius.context.channel" />
</service>

<service id="sylius.shop_api_plugin.controller.product.show_product_catalog_by_phrase_action"
class="Sylius\ShopApiPlugin\Controller\Product\ShowProductCatalogByPhraseAction"
>
<argument type="service" id="fos_rest.view_handler" />
<argument type="service" id="sylius.shop_api_plugin.view_repository.product_catalog_view_repository" />
<argument type="service" id="sylius.context.channel" />
</service>

<service id="sylius.shop_api_plugin.controller.product.show_product_reviews_by_slug_action"
class="Sylius\ShopApiPlugin\Controller\Product\ShowProductReviewsBySlugAction"
>
Expand Down
22 changes: 22 additions & 0 deletions src/ViewRepository/Product/ProductCatalogViewRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Sylius\ShopApiPlugin\ViewRepository\Product;

use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
Expand Down Expand Up @@ -82,6 +83,22 @@ public function findByTaxonCode(string $taxonCode, string $channelCode, Paginato
return $this->findByTaxon($taxon, $channel, $paginatorDetails, $localeCode);
}

public function findByPhrase(string $phrase, string $channelCode, PaginatorDetails $paginatorDetails, ?bool $includeDescription, ?string $localeCode): PageView
{
$channel = $this->getChannel($channelCode);
$localeCode = $this->supportedLocaleProvider->provide($localeCode, $channel);

$queryBuilder = $this->productRepository->createListQueryBuilder($localeCode);
$queryBuilder->andWhere('translation.name LIKE :phrase');
if (true === $includeDescription) {
$queryBuilder->orWhere('translation.description LIKE :phrase');
$queryBuilder->orWhere('translation.shortDescription LIKE :phrase');
}
$queryBuilder->setParameter('phrase', '%' . $phrase . '%');

return $this->createPageView($queryBuilder, $paginatorDetails, $channel, $localeCode);
}

private function getChannel(string $channelCode): ChannelInterface
{
/** @var ChannelInterface $channel */
Expand All @@ -98,6 +115,11 @@ private function findByTaxon(TaxonInterface $taxon, ChannelInterface $channel, P
$queryBuilder->addSelect('productTaxon');
$queryBuilder->addOrderBy('productTaxon.position');

return $this->createPageView($queryBuilder, $paginatorDetails, $channel, $localeCode);
}

private function createPageView(QueryBuilder $queryBuilder, PaginatorDetails $paginatorDetails, ChannelInterface $channel, string $localeCode): PageView
{
$pagerfanta = new Pagerfanta(new DoctrineORMAdapter($queryBuilder));

$pagerfanta->setMaxPerPage($paginatorDetails->limit());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ interface ProductCatalogViewRepositoryInterface
public function findByTaxonSlug(string $taxonSlug, string $channelCode, PaginatorDetails $paginatorDetails, ?string $localeCode): PageView;

public function findByTaxonCode(string $taxonCode, string $channelCode, PaginatorDetails $paginatorDetails, ?string $localeCode): PageView;

public function findByPhrase(string $phrase, string $channelCode, PaginatorDetails $paginatorDetails, ?bool $includeDescription, ?string $localeCode): PageView;
}
76 changes: 76 additions & 0 deletions tests/Controller/Product/ShowCatalogByNameApiTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Tests\Sylius\ShopApiPlugin\Controller\Product;

use Symfony\Component\HttpFoundation\Response;
use Tests\Sylius\ShopApiPlugin\Controller\JsonApiTestCase;

final class ShowCatalogByNameApiTest extends JsonApiTestCase
{
/**
CSchulz marked this conversation as resolved.
Show resolved Hide resolved
* @test
*/
public function it_shows_paginated_products_for_phrase(): void
{
$this->loadFixturesFromFiles(['shop.yml']);

$this->client->request('GET', '/shop-api/products/by-phrase/mug', [], [], self::CONTENT_TYPE_HEADER);
$response = $this->client->getResponse();

$this->assertResponse($response, 'product/product_list_page_by_phrase_mug_response', Response::HTTP_OK);
}

/**
* @test
*/
public function it_shows_paginated_products_for_phrase_including_description(): void
{
$this->loadFixturesFromFiles(['shop.yml']);

$this->client->request('GET', '/shop-api/products/by-phrase/Lorem', ['includeDescription' => true], [], self::CONTENT_TYPE_HEADER);
$response = $this->client->getResponse();

$this->assertResponse($response, 'product/product_list_page_by_phrase_mug_including_description_response', Response::HTTP_OK);
}

/**
* @test
*/
public function it_shows_paginated_products_for_phrase_including_short_description(): void
{
$this->loadFixturesFromFiles(['shop.yml']);

$this->client->request('GET', '/shop-api/products/by-phrase/short', ['includeDescription' => true], [], self::CONTENT_TYPE_HEADER);
$response = $this->client->getResponse();

$this->assertResponse($response, 'product/product_list_page_by_phrase_mug_including_short_description_response', Response::HTTP_OK);
}

/**
* @test
*/
public function it_shows_paginated_products_for_phrase_in_different_language(): void
{
$this->loadFixturesFromFiles(['shop.yml']);

$this->client->request('GET', '/shop-api/products/by-phrase/becher?locale=de_DE', [], [], self::CONTENT_TYPE_HEADER);
$response = $this->client->getResponse();

$this->assertResponse($response, 'product/product_list_page_by_phrase_mug_in_german_response', Response::HTTP_OK);
}

/**
* @test
*/
public function it_shows_paginated_products_for_phrase_in_different_language_including_description(): void
{
$this->loadFixturesFromFiles(['shop.yml']);

$this->client->request('GET', '/shop-api/products/by-phrase/Beschreibung?locale=de_DE', ['includeDescription' => true], [], self::CONTENT_TYPE_HEADER);
$response = $this->client->getResponse();

$this->assertResponse($response, 'product/product_list_page_by_phrase_mug_in_german_including_description_response', Response::HTTP_OK);
}
}
1 change: 1 addition & 0 deletions tests/DataFixtures/ORM/shop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Sylius\Component\Core\Model\ProductTranslation:
locale: "en_GB"
name: "Logan Shoes"
description: "Some description Lorem ipsum dolor sit amet."
shortDescription: "Short description Lorem ipsum dolor sit amet."
translatable: "@shoes"

Sylius\Component\Core\Model\ProductVariant:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@
"slug": "logan-shoes",
"channelCode": "WEB_GB",
"description": "Some description Lorem ipsum dolor sit amet.",
"shortDescription": "Short description Lorem ipsum dolor sit amet.",
"averageRating": 0,
"taxons": {
"others": []
Expand Down
Loading