diff --git a/.docker/fpm.conf b/.docker/fpm.conf new file mode 100644 index 00000000..4f0c372e --- /dev/null +++ b/.docker/fpm.conf @@ -0,0 +1,21 @@ +[www] +user = www-data +group = www-data + +listen = /var/run/php-www.sock +listen.owner = www-data +listen.group = www-data +listen.mode = 0660 + +clear_env = no + +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 + +pm.status_path = /status +catch_workers_output = yes + +security.limit_extensions = .php diff --git a/.docker/nginx.conf b/.docker/nginx.conf new file mode 100644 index 00000000..7fa27004 --- /dev/null +++ b/.docker/nginx.conf @@ -0,0 +1,48 @@ +user www-data; +worker_processes auto; +daemon off; +pid /run/nginx.pid; + +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server_tokens off; + + client_max_body_size 64m; + sendfile on; + tcp_nodelay on; + tcp_nopush on; + + gzip_vary on; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + server { + listen 80; + + root /app/tests/Application/public; + index index.php; + + location / { + try_files $uri /index.php$is_args$args; + } + + location ~ \.php$ { + include fastcgi_params; + + fastcgi_pass unix:/var/run/php-www.sock; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + } + } +} diff --git a/.docker/php.ini b/.docker/php.ini new file mode 100644 index 00000000..c283a534 --- /dev/null +++ b/.docker/php.ini @@ -0,0 +1,15 @@ +[PHP] +memory_limit=512M + +[date] +date.timezone=${PHP_DATE_TIMEZONE} + +[opcache] +opcache.enable=0 +opcache.memory_consumption=256 +opcache.max_accelerated_files=20000 +opcache.validate_timestamps=0 +;opcache.preload=/app/config/preload.php +opcache.preload_user=www-data +opcache.jit=1255 +opcache.jit_buffer_size=256M diff --git a/.docker/supervisord.conf b/.docker/supervisord.conf new file mode 100644 index 00000000..913adb67 --- /dev/null +++ b/.docker/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon = true +user = root +pidfile = /run/supervisord.pid + +[program:nginx] +command = /usr/sbin/nginx +user = root +autostart = true + +[program:php-fpm] +command = /usr/sbin/php-fpm -F +user = root +autostart = true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7b3eb6f..90c3f00a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,8 +22,8 @@ jobs: fail-fast: false matrix: php: [ "8.1", "8.2", "8.3" ] - symfony: [ "^5.4", "^6.0" ] - sylius: [ "~1.12.0", "~1.13.0" ] + symfony: [ "^6.0" ] + sylius: [ "~1.13.0" ] node: [ "18.x", "20.x" ] mysql: [ "5.7", "8.0" ] env: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..834732c5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,73 @@ +FROM ubuntu:20.04 +ARG DEBIAN_FRONTEND=noninteractive +ARG PHP_VERSION=8.1 +ENV LC_ALL=C.UTF-8 + +# Install basic tools +RUN apt-get update && apt-get install -y \ + software-properties-common \ + curl \ + make \ + supervisor \ + unzip \ + python2 \ + g++ + +# Append NODE, NGINX and PHP repositories +RUN add-apt-repository ppa:ondrej/php \ + && add-apt-repository ppa:ondrej/nginx \ + && curl -sL https://deb.nodesource.com/setup_14.x | bash - + +# Install required PHP extensions +RUN apt-get update && apt-get install -y \ + nodejs \ + nginx \ + php${PHP_VERSION} \ + php${PHP_VERSION}-apcu \ + php${PHP_VERSION}-calendar \ + php${PHP_VERSION}-common \ + php${PHP_VERSION}-cli \ + php${PHP_VERSION}-ctype \ + php${PHP_VERSION}-curl \ + php${PHP_VERSION}-dom \ + php${PHP_VERSION}-exif \ + php${PHP_VERSION}-fpm \ + php${PHP_VERSION}-gd \ + php${PHP_VERSION}-intl \ + php${PHP_VERSION}-mbstring \ + php${PHP_VERSION}-mysql \ + php${PHP_VERSION}-opcache \ + php${PHP_VERSION}-pdo \ + php${PHP_VERSION}-pgsql \ + php${PHP_VERSION}-sqlite \ + php${PHP_VERSION}-xml \ + php${PHP_VERSION}-xsl \ + php${PHP_VERSION}-yaml \ + php${PHP_VERSION}-zip + +# Install Composer +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename composer + +# Cleanup +RUN apt-get remove --purge -y software-properties-common curl && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* /usr/share/man/* + +# Create directory for php-fpm socket +# Link php-fpm binary file without version +# -p Creates missing intermediate path name directories +RUN ln -s /usr/sbin/php-fpm${PHP_VERSION} /usr/sbin/php-fpm && mkdir -p /run/php + +# Install yarn +RUN npm install -g yarn && npm cache clean --force + +# Initialize config files +COPY .docker/supervisord.conf /etc/supervisor/conf.d/supervisor.conf +COPY .docker/nginx.conf /etc/nginx/nginx.conf +COPY .docker/fpm.conf /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf +COPY .docker/php.ini /etc/php/${PHP_VERSION}/fpm/php.ini +COPY .docker/php.ini /etc/php/${PHP_VERSION}/cli/php.ini + +WORKDIR /app + +EXPOSE 80 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/README.md b/README.md index 667140eb..5bdba573 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,18 @@ This **open-source plugin was developed to help the Sylius community**. If you h [![](https://bitbag.io/wp-content/uploads/2020/10/button-contact.png)](https://bitbag.io/contact-us/?utm_source=github&utm_medium=referral&utm_campaign=plugins_elasticsearch) +# Requirements + +---- + +This plugin requires elasticsearch server running. You can install it by following the instructions on the [official website](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html). +In plugin repository there is Docker Compose file that can be used to run Elasticsearch server. # Installation ---- We work on stable, supported and up-to-date versions of packages. We recommend you to do the same. -If you use Sylius 1.4, you might get a compatibility issue for Pagerfanta. Please read [this issue](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/issues/23) in order to proceed with a workaround. *Note*: This Plugin supports ElasticSearch 7.0 and above. If you're looking for ElasticSearch Plugin for older versions check SyliusElasticSearchPlugin in version `1.x`. @@ -81,7 +86,6 @@ The final effect should look like the following: ``` use BitBag\SyliusElasticsearchPlugin\Model\ProductVariantInterface as BitBagElasticsearchPluginVariant; use BitBag\SyliusElasticsearchPlugin\Model\ProductVariantTrait; -use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductVariantInterface as BaseProductVariantInterface; class ProductVariant extends BaseProductVariant implements BaseProductVariantInterface, BitBagElasticsearchPluginVariant @@ -134,7 +138,7 @@ $ bin/console assets:install fos_elastica: clients: - default: { host: localhost, port: 9200 } + default: { url: '%env(ELASTICSEARCH_URL)%' } indexes: app: ~ ``` @@ -143,7 +147,7 @@ should become: fos_elastica: clients: - default: { host: localhost, port: 9200 } + default: { url: '%env(ELASTICSEARCH_URL)%' } ``` In the end, with an elasticsearch server running, execute following commands: ``` @@ -203,12 +207,35 @@ webpack_encore: ## Usage -### Rendering the shop products list + +### Scope of the search + +This plugin offers a site-wide search feature and taxon search feature. It is easily extendable to add more search scopes. For example in Marketplace suite you can create Vendor specific search scope. + +### Searching site-wide products + +There is searchbar in the header of the shop. + +
+ +
+ +You can easily modify it by overriding the `@BitBagSyliusElasticsearchPlugin/Shop/Menu/_searchForm.html.twig` template or disable it by setting: +```yml +sylius_ui: + events: + sylius.shop.layout.header.content: + blocks: + bitbag_es_search_form: + enabled: false +``` + +### Searching taxon products When you go now to the `/{_locale}/products-list/{taxon-slug}` page, you should see a totally new set of filters. You should see something like this:
- +
You might also want to refer the horizontal menu to a new product list page. Follow below instructions to do so: @@ -229,12 +256,52 @@ If you're using vertical menu - follow steps above with `_verticalMenu.html.twig You might not want to show some specific options or attributes in the menu. You can set specific parameters for that: ```yml parameters: - bitbag_es_excluded_filter_options: [] - bitbag_es_excluded_filter_attributes: ['book_isbn', 'book_pages'] + bitbag_es_excluded_facet_attributes: ['jeans_material'] + bitbag_es_excluded_facet_options: ['t_shirt_size'] +``` + +By default, all options and attributes filters are shown. + +It is also possible to disable options and attribute filters autodiscovery by setting the following parameters: +```yml +parameters: + bitbag_es_facets_auto_discover: false ``` -By default, all options and attributes are indexed. After you change these parameters, remember to run `bin/console fo:el:po` command again -(a shortcut for `fos:elastica:populate`). +Then you have to manually register your filters: + +Available filters: +* [`TaxonFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/TaxonFacet.php) which allows to filter your search results by taxons using the ElasticSearch [`Terms`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html) aggregation. +* [`AttributeFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/AttributeFacet.php) which allows to filter your search results by product attributes values using the ElasticSearch [`Terms`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html) aggregation. +* [`OptionFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/OptionFacet.php) which is the same as `AttributeFacet` but for product options. +* [`PriceFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/PriceFacet.php) which allows to filter search results by price range the ElasticSearch [`Histogram`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html) aggregation. + +Example of manual registration of filters: +```yml +services: + bitbag_sylius_elasticsearch_plugin.facet.attribute.t_shirt_brand: + class: BitBag\SyliusElasticsearchPlugin\Facet\AttributeFacet + arguments: + - '@bitbag_sylius_elasticsearch_plugin.property_name_resolver.attribute' + - '@=service("sylius.repository.product_attribute").findOneBy({"code": "t_shirt_brand"})' + - '@sylius.context.locale' + + bitbag_sylius_elasticsearch_plugin.facet.registry: + class: BitBag\SyliusElasticsearchPlugin\Facet\Registry + calls: + - method: addFacet + arguments: + - t_shirt_brand + - '@bitbag_sylius_elasticsearch_plugin.facet.attribute.t_shirt_brand' + - method: addFacet + arguments: + - price + - '@bitbag_sylius_elasticsearch_plugin.facet.price' + - method: addFacet + arguments: + - taxon + - '@bitbag_sylius_elasticsearch_plugin.facet.taxon' +``` ### Reindexing @@ -254,20 +321,6 @@ fos_elastica: Indexes with `bitbag_shop_product`, `bitbag_attribute_taxons` and `bitbag_option_taxons` keys are available so far. -### Site-wide search - -This plugin offers a site-wide search feature as well. You have a search box field where you query all products indexed on ElasticSearch. When you enter a query in the search box the results will appear in the search results page. - -### Facets - -You can also add search facets (a.k.a. filters) to your search results page, both taxon and site-wide search. To do so you have to add facets to the `bitbag_sylius_elasticsearch_plugin.facet.registry` (for site-wide search) or `bitbag_sylius_elasticsearch_plugin.facet.taxon_registry` (for taxon search) service (see an example of those service definitions [here](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/tests/Application/config/services.yaml)). A facet is a service which implements the `BitBag\SyliusElasticsearchPlugin\Facet\FacetInterface`. You can implement your own facets from scratch or you can [decorate](https://symfony.com/doc/current/service_container/service_decoration.html) one of the basic facet implementation included in this plugin, which are: - -* [`TaxonFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/TaxonFacet.php) which allows to filter your search results by taxons using the ElasticSearch [`Terms`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html) aggregation. -* [`AttributeFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/AttributeFacet.php) which allows to filter your search results by product attributes values using the ElasticSearch [`Terms`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html) aggregation. -* [`OptionFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/OptionFacet.php) which is the same as `AttributeFacet` but for product options. -* [`PriceFacet`](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/src/Facet/PriceFacet.php) which allows to filter search results by price range the ElasticSearch [`Histogram`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html) aggregation. - -You can see an example of the definition of all of these facets [here](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin/blob/master/tests/Application/config/services.yaml). ## Customization @@ -307,10 +360,10 @@ We build **unforgettable**, consistent digital customer journeys on top of the * Our team is fluent in **Polish, English, German and, French**. That is why our cooperation with clients from all over the world is smooth. **Some numbers from BitBag regarding Sylius:** -- 50+ **experts** including consultants, UI/UX designers, Sylius trained front-end and back-end developers, -- 120+ projects **delivered** on top of Sylius, -- 25+ **countries** of BitBag’s customers, -- 4+ **years** in the Sylius ecosystem. +- 70+ **experts** including consultants, UI/UX designers, Sylius trained front-end and back-end developers, +- 150+ projects **delivered** on top of Sylius, +- 30+ **countries** of BitBag’s customers, +- 7+ **years** in the Sylius ecosystem. **Our services:** - Business audit/Consulting in the field of **strategy** development, diff --git a/UPGRADE-4.0.0.md b/UPGRADE-4.0.0.md new file mode 100644 index 00000000..84d43750 --- /dev/null +++ b/UPGRADE-4.0.0.md @@ -0,0 +1,7 @@ +# UPGRADE FROM 3.* TO 4.0.0 + +#### Changes related to facets: + +Since version 4 standard filters are deprecated, and replaced by facets. Facets are a more flexible and powerful way to filter products in the search engine. The main difference is that facets are not limited to the predefined values of the attribute, but they are generated based on the values of the products in the index. This means that the user can filter products by any value of the attribute, not only the predefined ones. + +If you still want to use the old filters, you have to manually add them in form extension - they are still in the code: `BitBag\SyliusElasticsearchPlugin\Form\Type\ProductAttributesFilterType` and `BitBag\SyliusElasticsearchPlugin\Form\Type\ProductOptionsFilterType`. diff --git a/composer.json b/composer.json index 1a8a3399..71642b00 100644 --- a/composer.json +++ b/composer.json @@ -4,10 +4,10 @@ "description": "BitBag Elasticsearch plugin for Sylius.", "license": "MIT", "require": { + "php": "^8.1", "ext-json": "*", - "php": "^8.0", "friendsofsymfony/elastica-bundle": "^6.0", - "sylius/sylius": ">=1.12.13 || ~1.13.0", + "sylius/sylius": "~1.13.0", "symfony/property-access": "^5.4 || ^6.0", "symfony/webpack-encore-bundle": "^1.15", "symfony/proxy-manager-bridge": "^5.4 || ^6.0" @@ -35,7 +35,7 @@ "phpstan/phpstan-doctrine": "1.3.69", "phpstan/phpstan-strict-rules": "^1.3.0", "phpstan/phpstan-webmozart-assert": "^1.2.0", - "phpunit/phpunit": "^10.5", + "phpunit/phpunit": "^9.0 || ^10.0", "polishsymfonycommunity/symfony-mocker-container": "^1.0", "robertfausk/behat-panther-extension": "^1.1", "sylius-labs/coding-standard": "^4.2", diff --git a/doc/es_browser.png b/doc/es_browser.png new file mode 100644 index 00000000..e59a99e7 Binary files /dev/null and b/doc/es_browser.png differ diff --git a/doc/es_results.png b/doc/es_results.png new file mode 100644 index 00000000..97aaf98a Binary files /dev/null and b/doc/es_results.png differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..4bde7483 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +services: + app: + container_name: app + build: + context: . + environment: + APP_ENV: "dev" + DATABASE_URL: "mysql://root:mysql@mysql/sylius_%kernel.environment%?charset=utf8mb4" +# DATABASE_URL: "pgsql://root:postgres@postgres/sylius_%kernel.environment%?charset=utf8" # When using postgres + PHP_DATE_TIMEZONE: "Europe/Warsaw" + ELASTICSEARCH_URL: "http://elasticsearch:9200/" + volumes: + - ./:/app:delegated + - ./.docker/php.ini:/etc/php8/php.ini:delegated + - ./.docker/nginx.conf:/etc/nginx/nginx.conf:delegated + ports: + - 80:80 + depends_on: + - mysql + networks: + - sylius + + mysql: + container_name: mysql + image: mysql:8.0 + platform: linux/amd64 + environment: + MYSQL_ROOT_PASSWORD: mysql + ports: + - ${MYSQL_PORT:-3306}:3306 + networks: + - sylius + +# postgres: +# image: postgres:14-alpine +# environment: +# POSTGRES_USER: root +# POSTGRES_PASSWORD: postgres +# ports: +# - ${POSTGRES_PORT:-5432}:5432 +# networks: +# - sylius + + elasticsearch: + container_name: elasticsearch + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5 + environment: + discovery.type: "single-node" + ES_JAVA_OPTS: -Xms512m -Xmx512m + ports: + - "9200:9200" + - "9300:9300" + networks: + - sylius + +networks: + sylius: + driver: bridge diff --git a/features/shop/searching_products_by_a_partial_name.feature b/features/shop/searching_products_by_a_partial_name.feature index e4bcd756..44cb3fb3 100644 --- a/features/shop/searching_products_by_a_partial_name.feature +++ b/features/shop/searching_products_by_a_partial_name.feature @@ -6,15 +6,17 @@ Feature: Filtering products Background: Given the store operates on a channel named "Web-US" in "USD" currency - And the store classifies its products as "Cars" - And there is a product named "Volksvagen Polo" in the store - And there is a product named "Volvo XC90" in the store - And there is a product named "Polonez Caro" in the store - And there is a product named "Porsche Carrera GT" in the store - And these products belongs primarily to "Cars" taxon + And the store classifies its products as "Shirts" + And there is a product named "Loose white designer T-Shirt" in the store + And there is a product named "Everyday white basic T-Shirt" in the store + And there is a product named "Ribbed copper slim fit Tee" in the store + And there is a product named "Oversize white cotton T-Shirt" in the store + And there is a product named "Raglan grey & black Tee" in the store + And there is a product named "Sport basic white T-Shirt" in the store + And these products belongs primarily to "Shirts" taxon And the data is populated to Elasticsearch @api Scenario: Filtering products by name - When I search the products by "vol" phrase - Then I should see 2 products + When I search the products by "shirt" phrase + Then I should see 4 products diff --git a/features/shop/site_wide_searching_products.feature b/features/shop/site_wide_searching_products.feature index 41dd6cc3..c0bb9706 100644 --- a/features/shop/site_wide_searching_products.feature +++ b/features/shop/site_wide_searching_products.feature @@ -11,34 +11,34 @@ Feature: Site-wide products search And the store has a select product attribute "Car Type" with values "Cabrio" and "SUV" And the store has a select product attribute "Motorbike Type" with values "Enduro" and "Naked" And there is a product named "BMW Z4" in the store - And this product's price is "$42670" + And this product's price is "$42,670.00" And this product has select attribute "Car Type" with value "Cabrio" And this product has a text attribute "Color" with value "Red" And this product has option "Supply" with values "Gasoline" and "Diesel" - And this product is available in "Gasoline" supply priced at "$42670" - And this product is available in "Diesel" supply priced at "$45670" + And this product is available in "Gasoline" supply priced at "$42,670.00" + And this product is available in "Diesel" supply priced at "$45,670.00" And this product belongs to "Cars" And there is a product named "Volvo XC90" in the store - And this product's price is "$64505.80" + And this product's price is "$64,505.80" And this product has select attribute "Car Type" with value "SUV" And this product has a text attribute "Color" with value "Black" And this product belongs to "Cars" And there is a product named "BMW 5 Series" in the store - And this product's price is "$52070" + And this product's price is "$52,070.00" And this product has select attribute "Car Type" with value "Cabrio" And this product has a text attribute "Color" with value "Red" And this product belongs to "Cars" And there is a product named "Lamborghini Aventador" in the store - And this product's price is "$450000" + And this product's price is "$450,000.00" And this product has a text attribute "Color" with value "Yellow" And this product belongs to "Cars" And there is a product named "BMW GS" in the store - And this product's price is "$18070" + And this product's price is "$18,070.00" And this product has select attribute "Motorbike Type" with value "Enduro" And this product has a text attribute "Color" with value "Grey" And this product belongs to "Motorbikes" And there is a product named "Ducati Monster" in the store - And this product's price is "$14995" + And this product's price is "$14,995.00" And this product has select attribute "Motorbike Type" with value "Naked" And this product has a text attribute "Color" with value "Black" And this product's short description is: @@ -49,7 +49,7 @@ Feature: Site-wide products search And there is a product named "Honda Africa Twin" in the store And this product has select attribute "Motorbike Type" with value "Enduro" And this product has a text attribute "Color" with value "Green & White" - And this product's price is "$13490" + And this product's price is "$13,490.00" And this product's description is: """ This is the Honda Africa Twin which is like the BMW GS but from Honda. @@ -101,8 +101,7 @@ Feature: Site-wide products search When I browse the search page And I search the products by "BMW" phrase in the site-wide search box And I filter by price interval "$10,000.00 - $20,000.00" - And I filter by price interval "$50,000.00 - $60,000.00" - Then I should see 4 products in search results + Then I should see 3 products in search results @ui Scenario: Searching products and filtering by taxon diff --git a/features/shop/site_wide_searching_products_113.feature b/features/shop/site_wide_searching_products_113.feature index 7ab7cc43..8f128531 100644 --- a/features/shop/site_wide_searching_products_113.feature +++ b/features/shop/site_wide_searching_products_113.feature @@ -101,8 +101,7 @@ Feature: Site-wide products search When I browse the search page And I search the products by "BMW" phrase in the site-wide search box And I filter by price interval "$10,000.00 - $20,000.00" - And I filter by price interval "$50,000.00 - $60,000.00" - Then I should see 4 products in search results + Then I should see 3 products in search results @ui Scenario: Searching products and filtering by taxon diff --git a/spec/Controller/Action/Shop/ListProductsActionSpec.php b/spec/Controller/Action/Shop/TaxonProductsSearchActionSpec.php similarity index 61% rename from spec/Controller/Action/Shop/ListProductsActionSpec.php rename to spec/Controller/Action/Shop/TaxonProductsSearchActionSpec.php index 30f9b070..d1ffb405 100644 --- a/spec/Controller/Action/Shop/ListProductsActionSpec.php +++ b/spec/Controller/Action/Shop/TaxonProductsSearchActionSpec.php @@ -12,10 +12,8 @@ namespace spec\BitBag\SyliusElasticsearchPlugin\Controller\Action\Shop; -use BitBag\SyliusElasticsearchPlugin\Controller\Action\Shop\ListProductsAction; +use BitBag\SyliusElasticsearchPlugin\Controller\Action\Shop\TaxonProductsSearchAction; use BitBag\SyliusElasticsearchPlugin\Controller\RequestDataHandler\DataHandlerInterface; -use BitBag\SyliusElasticsearchPlugin\Controller\RequestDataHandler\PaginationDataHandlerInterface; -use BitBag\SyliusElasticsearchPlugin\Controller\RequestDataHandler\SortDataHandlerInterface; use BitBag\SyliusElasticsearchPlugin\Finder\ShopProductsFinderInterface; use BitBag\SyliusElasticsearchPlugin\Form\Type\ShopProductsFilterType; use Pagerfanta\Pagerfanta; @@ -29,29 +27,25 @@ use Symfony\Component\HttpFoundation\Response; use Twig\Environment; -final class ListProductsActionSpec extends ObjectBehavior +final class TaxonProductsSearchActionSpec extends ObjectBehavior { function let( FormFactoryInterface $formFactory, - DataHandlerInterface $shopProductListDataHandler, - SortDataHandlerInterface $shopProductsSortDataHandler, - PaginationDataHandlerInterface $paginationDataHandler, - ShopProductsFinderInterface $shopProductsFinder, + DataHandlerInterface $dataHandler, + ShopProductsFinderInterface $finder, Environment $twig ): void { $this->beConstructedWith( $formFactory, - $shopProductListDataHandler, - $shopProductsSortDataHandler, - $paginationDataHandler, - $shopProductsFinder, + $dataHandler, + $finder, $twig ); } function it_is_initializable(): void { - $this->shouldHaveType(ListProductsAction::class); + $this->shouldHaveType(TaxonProductsSearchAction::class); } function it_renders_product_list( @@ -59,14 +53,12 @@ function it_renders_product_list( FormFactoryInterface $formFactory, FormInterface $form, ParameterBag $queryParameters, - DataHandlerInterface $shopProductListDataHandler, - SortDataHandlerInterface $shopProductsSortDataHandler, - PaginationDataHandlerInterface $paginationDataHandler, - ShopProductsFinderInterface $shopProductsFinder, + DataHandlerInterface $dataHandler, Pagerfanta $pagerfanta, FormView $formView, Environment $twig, - Response $response + Response $response, + ShopProductsFinderInterface $finder, ): void { $form->getData()->willReturn([]); $form->isValid()->willReturn(true); @@ -81,11 +73,9 @@ function it_renders_product_list( $request->get('template')->willReturn('@Template'); $request->get('slug')->willReturn(null); - $shopProductListDataHandler->retrieveData(['slug' => null])->willReturn(['taxon' => null]); - $shopProductsSortDataHandler->retrieveData(['slug' => null]); - $paginationDataHandler->retrieveData(['slug' => null]); + $dataHandler->retrieveData(['slug' => null])->willReturn(['taxon' => null]); - $shopProductsFinder->find(['taxon' => null])->willReturn($pagerfanta); + $finder->find(['taxon' => null])->willReturn($pagerfanta); $twig->render('@Template', Argument::any())->shouldBeCalled(); diff --git a/spec/Controller/RequestDataHandler/ShopProductsSortDataHandlerSpec.php b/spec/Controller/RequestDataHandler/ShopProductsSortDataHandlerSpec.php index 08a659c5..161ecabc 100644 --- a/spec/Controller/RequestDataHandler/ShopProductsSortDataHandlerSpec.php +++ b/spec/Controller/RequestDataHandler/ShopProductsSortDataHandlerSpec.php @@ -23,14 +23,10 @@ final class ShopProductsSortDataHandlerSpec extends ObjectBehavior public function let( ConcatedNameResolverInterface $channelPricingNameResolver, ChannelContextInterface $channelContext, - TaxonContextInterface $taxonContext, - ConcatedNameResolverInterface $taxonPositionNameResolver ): void { $this->beConstructedWith( $channelPricingNameResolver, $channelContext, - $taxonContext, - $taxonPositionNameResolver, 'sold_units', 'created_at', 'price' @@ -50,15 +46,13 @@ function it_implements_sort_data_handler_interface(): void function it_retrieves_data( TaxonContextInterface $taxonContext, TaxonInterface $taxon, - ConcatedNameResolverInterface $taxonPositionNameResolver ): void { $taxonContext->getTaxon()->willReturn($taxon); $taxon->getCode()->willReturn('t_shirt'); - $taxonPositionNameResolver->resolvePropertyName('t_shirt')->willReturn('taxon_position_t_shirts'); $this->retrieveData([])->shouldBeEqualTo([ 'sort' => [ - 'taxon_position_t_shirts' => [ + 'created_at' => [ 'order' => SortDataHandlerInterface::SORT_ASC_INDEX, 'unmapped_type' => 'keyword', ], diff --git a/spec/Facet/AttributeFacetSpec.php b/spec/Facet/AttributeFacetSpec.php index 728adee8..38297226 100644 --- a/spec/Facet/AttributeFacetSpec.php +++ b/spec/Facet/AttributeFacetSpec.php @@ -9,18 +9,19 @@ use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\ConcatedNameResolverInterface; use Elastica\Aggregation\Terms; use PhpSpec\ObjectBehavior; +use Sylius\Component\Attribute\Model\AttributeInterface; use Sylius\Component\Locale\Context\LocaleContextInterface; -use Sylius\Component\Resource\Repository\RepositoryInterface; final class AttributeFacetSpec extends ObjectBehavior { function let( ConcatedNameResolverInterface $attributeNameResolver, - RepositoryInterface $attributeRepository, + AttributeInterface $attribute, LocaleContextInterface $localeContext ): void { $attributeNameResolver->resolvePropertyName('attribute_code')->willReturn('attribute_attribute_code'); - $this->beConstructedWith($attributeNameResolver, $attributeRepository, 'attribute_code', $localeContext); + $attribute->getCode()->willReturn('attribute_code'); + $this->beConstructedWith($attributeNameResolver, $attribute, $localeContext); } function it_is_initializable(): void diff --git a/spec/Facet/OptionFacetSpec.php b/spec/Facet/OptionFacetSpec.php index dc9de57d..c66540d6 100644 --- a/spec/Facet/OptionFacetSpec.php +++ b/spec/Facet/OptionFacetSpec.php @@ -9,20 +9,18 @@ use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\ConcatedNameResolverInterface; use Elastica\Aggregation\Terms; use PhpSpec\ObjectBehavior; -use Sylius\Component\Product\Model\ProductOption; -use Sylius\Component\Resource\Repository\RepositoryInterface; +use Sylius\Component\Product\Model\ProductOptionInterface; final class OptionFacetSpec extends ObjectBehavior { - function let(ConcatedNameResolverInterface $optionNameResolver, RepositoryInterface $productOptionRepository): void + function let(ConcatedNameResolverInterface $optionNameResolver, ProductOptionInterface $productOption): void { $optionCode = 'SUPPLY'; $optionNameResolver->resolvePropertyName('SUPPLY')->willReturn('option_SUPPLY'); - $productOption = new ProductOption(); $productOption->setCurrentLocale('en_US'); - $productOption->setName('Supply'); - $productOptionRepository->findOneBy(['code' => 'SUPPLY'])->willReturn($productOption); - $this->beConstructedWith($optionNameResolver, $productOptionRepository, $optionCode); + $productOption->getName()->willReturn('Supply'); + $productOption->getCode()->willReturn($optionCode); + $this->beConstructedWith($optionNameResolver, $productOption, $optionCode); } function it_is_initializable(): void diff --git a/spec/Finder/NamedProductsFinderSpec.php b/spec/Finder/NamedProductsFinderSpec.php index 5ea8d00a..4f81b48b 100644 --- a/spec/Finder/NamedProductsFinderSpec.php +++ b/spec/Finder/NamedProductsFinderSpec.php @@ -37,7 +37,7 @@ function it_finds_by_partial_name_of_products( FinderInterface $productsFinder, AbstractQuery $query ): void { - $productsByPartialNameQueryBuilder->buildQuery(['name' => 'part'])->willReturn($query); + $productsByPartialNameQueryBuilder->buildQuery(['query' => 'part'])->willReturn($query); $productsFinder->find($query)->willReturn([]); diff --git a/spec/QueryBuilder/ContainsNameQueryBuilderSpec.php b/spec/QueryBuilder/ContainsNameQueryBuilderSpec.php index 5c9bb195..517c96a0 100644 --- a/spec/QueryBuilder/ContainsNameQueryBuilderSpec.php +++ b/spec/QueryBuilder/ContainsNameQueryBuilderSpec.php @@ -11,9 +11,10 @@ namespace spec\BitBag\SyliusElasticsearchPlugin\QueryBuilder; use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\ConcatedNameResolverInterface; +use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\SearchPropertyNameResolverRegistryInterface; use BitBag\SyliusElasticsearchPlugin\QueryBuilder\ContainsNameQueryBuilder; use BitBag\SyliusElasticsearchPlugin\QueryBuilder\QueryBuilderInterface; -use Elastica\Query\MatchQuery; +use Elastica\Query\MultiMatch; use PhpSpec\ObjectBehavior; use Sylius\Component\Locale\Context\LocaleContextInterface; @@ -21,13 +22,16 @@ final class ContainsNameQueryBuilderSpec extends ObjectBehavior { function let( LocaleContextInterface $localeContext, + SearchPropertyNameResolverRegistryInterface $searchPropertyNameResolverRegistry, ConcatedNameResolverInterface $productNameNameResolver ): void { $this->beConstructedWith( $localeContext, - $productNameNameResolver, - 'name_property' + $searchPropertyNameResolverRegistry, + 'AUTO' ); + + $searchPropertyNameResolverRegistry->getPropertyNameResolvers()->willReturn([$productNameNameResolver]); } function it_is_initializable(): void @@ -42,23 +46,30 @@ function it_implements_query_builder_interface(): void function it_builds_query( LocaleContextInterface $localeContext, + SearchPropertyNameResolverRegistryInterface $searchPropertyNameResolverRegistry, ConcatedNameResolverInterface $productNameNameResolver ): void { $localeContext->getLocaleCode()->willReturn('en'); - $productNameNameResolver->resolvePropertyName('en')->willReturn('en'); + $productNameNameResolver->resolvePropertyName('en')->willReturn('name_en'); + + $searchPropertyNameResolverRegistry->getPropertyNameResolvers()->willReturn([$productNameNameResolver]); - $this->buildQuery(['name_property' => 'Book'])->shouldBeAnInstanceOf(MatchQuery::class); + $query = $this->buildQuery(['name' => 'Book']); + $query->shouldBeAnInstanceOf(MultiMatch::class); } - function it_builds_returned_null_if_property_is_null( + function it_returns_null_when_no_query( LocaleContextInterface $localeContext, + SearchPropertyNameResolverRegistryInterface $searchPropertyNameResolverRegistry, ConcatedNameResolverInterface $productNameNameResolver ): void { $localeContext->getLocaleCode()->willReturn('en'); - $productNameNameResolver->resolvePropertyName('en')->willReturn('en'); + $productNameNameResolver->resolvePropertyName('en')->willReturn('name_en'); + + $searchPropertyNameResolverRegistry->getPropertyNameResolvers()->willReturn([$productNameNameResolver]); - $this->buildQuery(['name_property' => null])->shouldBeEqualTo(null); + $this->buildQuery([])->shouldReturn(null); } } diff --git a/spec/QueryBuilder/SearchProductsQueryBuilderSpec.php b/spec/QueryBuilder/SearchProductsQueryBuilderSpec.php deleted file mode 100644 index 1b13ad78..00000000 --- a/spec/QueryBuilder/SearchProductsQueryBuilderSpec.php +++ /dev/null @@ -1,105 +0,0 @@ -getLocaleCode()->willReturn('en_US'); - $searchPropertyNameResolverRegistry->getPropertyNameResolvers()->willReturn([]); - $this->isEnabeldQuery = new Term(); - $this->isEnabeldQuery->setTerm('enabled', true); - $isEnabledQueryBuilder->buildQuery([])->willReturn($this->isEnabeldQuery); - $this->hasChannelQuery = new Terms('channels'); - $this->hasChannelQuery->setTerms(['web_us']); - $hasChannelQueryBuilder->buildQuery([])->willReturn($this->hasChannelQuery); - $this->fuzziness = 'AUTO'; - $this->beConstructedWith( - $searchPropertyNameResolverRegistry, - $localeContext, - $isEnabledQueryBuilder, - $hasChannelQueryBuilder, - $this->fuzziness - ); - } - - function it_is_initializable(): void - { - $this->shouldHaveType(SearchProductsQueryBuilder::class); - } - - function it_implements_query_builder_interface(): void - { - $this->shouldHaveType(QueryBuilderInterface::class); - } - - function it_throws_an_exception_if_query_is_not_present_in_data(): void - { - $this->shouldThrow(\RuntimeException::class)->during('buildQuery', [['not_relevant_key' => 'value']]); - } - - function it_throws_an_exception_if_query_is_not_a_string(): void - { - $this->shouldThrow(\RuntimeException::class)->during('buildQuery', [['query' => new \stdClass()]]); - } - - function it_builds_multi_match_query_with_provided_query_string(): void - { - $expectedMultiMatch = new MultiMatch(); - $expectedMultiMatch->setQuery('bmw'); - $expectedMultiMatch->setFuzziness($this->fuzziness); - $expectedMultiMatch->setFields([]); - $expectedQuery = new BoolQuery(); - $expectedQuery->addMust($expectedMultiMatch); - $expectedQuery->addFilter($this->isEnabeldQuery); - $expectedQuery->addFilter($this->hasChannelQuery); - - $this->buildQuery(['query' => 'bmw'])->shouldBeLike($expectedQuery); - } - - function it_builds_multi_match_query_with_provided_query_string_and_fields_from_registry( - SearchPropertyNameResolverRegistryInterface $searchPropertyNameResolverRegistry, - ConcatedNameResolverInterface $firstPropertyNameResolver, - ConcatedNameResolverInterface $secondPropertyNameResolver - ): void { - $firstPropertyNameResolver->resolvePropertyName('en_US')->shouldBeCalled()->willReturn('property_1_en_us'); - $secondPropertyNameResolver->resolvePropertyName('en_US')->shouldBeCalled()->willReturn('property_2_en_us'); - $searchPropertyNameResolverRegistry->getPropertyNameResolvers()->willReturn( - [$firstPropertyNameResolver, $secondPropertyNameResolver] - ); - $expectedMultiMatch = new MultiMatch(); - $expectedMultiMatch->setQuery('bmw'); - $expectedMultiMatch->setFuzziness($this->fuzziness); - $expectedMultiMatch->setFields(['property_1_en_us', 'property_2_en_us']); - $expectedQuery = new BoolQuery(); - $expectedQuery->addMust($expectedMultiMatch); - $expectedQuery->addFilter($this->isEnabeldQuery); - $expectedQuery->addFilter($this->hasChannelQuery); - - $this->buildQuery(['query' => 'bmw'])->shouldBeLike($expectedQuery); - } -} diff --git a/spec/QueryBuilder/SiteWideProductsQueryBuilderSpec.php b/spec/QueryBuilder/SiteWideProductsQueryBuilderSpec.php new file mode 100644 index 00000000..62a037ab --- /dev/null +++ b/spec/QueryBuilder/SiteWideProductsQueryBuilderSpec.php @@ -0,0 +1,74 @@ +isEnabeldQuery = new Term(); + $this->isEnabeldQuery->setTerm('enabled', true); + $isEnabledQueryBuilder->buildQuery([])->willReturn($this->isEnabeldQuery); + + $this->hasChannelQuery = new Terms('channels'); + $this->hasChannelQuery->setTerms(['web_us']); + $hasChannelQueryBuilder->buildQuery([])->willReturn($this->hasChannelQuery); + + $this->containsNameQuery = new MultiMatch(); + $this->containsNameQuery->setQuery('bmw'); + $containsNameQueryBuilder->buildQuery(['query' => 'bmw'])->willReturn($this->containsNameQuery); + + $this->beConstructedWith( + $isEnabledQueryBuilder, + $hasChannelQueryBuilder, + $containsNameQueryBuilder + ); + } + + function it_is_initializable(): void + { + $this->shouldHaveType(SiteWideProductsQueryBuilder::class); + } + + function it_implements_query_builder_interface(): void + { + $this->shouldHaveType(QueryBuilderInterface::class); + } + + function it_builds_multi_match_query_with_provided_query_string(): void + { + $expectedQuery = new BoolQuery(); + $expectedQuery->addMust($this->isEnabeldQuery); + $expectedQuery->addMust($this->hasChannelQuery); + $expectedQuery->addMust($this->containsNameQuery); + + $this->buildQuery(['query' => 'bmw'])->shouldBeLike($expectedQuery); + } +} diff --git a/spec/QueryBuilder/ShopProductsQueryBuilderSpec.php b/spec/QueryBuilder/TaxonProductsQueryBuilderSpec.php similarity index 93% rename from spec/QueryBuilder/ShopProductsQueryBuilderSpec.php rename to spec/QueryBuilder/TaxonProductsQueryBuilderSpec.php index 65e5a28b..9ed763e6 100644 --- a/spec/QueryBuilder/ShopProductsQueryBuilderSpec.php +++ b/spec/QueryBuilder/TaxonProductsQueryBuilderSpec.php @@ -11,12 +11,12 @@ namespace spec\BitBag\SyliusElasticsearchPlugin\QueryBuilder; use BitBag\SyliusElasticsearchPlugin\QueryBuilder\QueryBuilderInterface; -use BitBag\SyliusElasticsearchPlugin\QueryBuilder\ShopProductsQueryBuilder; +use BitBag\SyliusElasticsearchPlugin\QueryBuilder\TaxonProductsQueryBuilder; use Elastica\Query\AbstractQuery; use Elastica\Query\BoolQuery; use PhpSpec\ObjectBehavior; -final class ShopProductsQueryBuilderSpec extends ObjectBehavior +final class TaxonProductsQueryBuilderSpec extends ObjectBehavior { function let( QueryBuilderInterface $isEnabledQueryBuilder, @@ -42,7 +42,7 @@ function let( function it_is_initializable(): void { - $this->shouldHaveType(ShopProductsQueryBuilder::class); + $this->shouldHaveType(TaxonProductsQueryBuilder::class); } function it_implements_query_builder_interface(): void diff --git a/src/Block/SearchFormEventListener.php b/src/Block/SearchFormEventListener.php deleted file mode 100644 index 98a3893a..00000000 --- a/src/Block/SearchFormEventListener.php +++ /dev/null @@ -1,77 +0,0 @@ -template = $template; - $this->formFactory = $formFactory; - $this->router = $router; - } - - public function onBlockEvent(BlockEvent $event): void - { - $block = new Block(); - $block->setId(uniqid('', true)); - $block->setSettings( - array_replace( - $event->getSettings(), - [ - 'template' => $this->template, - 'form' => $this->getForm()->createView(), - ] - ) - ); - $block->setType('sonata.block.service.template'); - - $event->addBlock($block); - } - - public function getForm(Search $search = null): FormInterface - { - if (!$this->form) { - if (!$search) { - $search = new Search(); - } - $this->form = $this->formFactory - ->create( - SearchType::class, - $search, - ['action' => $this->router->generate('bitbag_sylius_elasticsearch_plugin_shop_search')] - ); - } - - return $this->form; - } -} diff --git a/src/Context/ProductAttributesContext.php b/src/Context/ProductAttributesContext.php index 7414ad1d..b36dcdaf 100644 --- a/src/Context/ProductAttributesContext.php +++ b/src/Context/ProductAttributesContext.php @@ -16,16 +16,10 @@ final class ProductAttributesContext implements ProductAttributesContextInterface { - private TaxonContextInterface $taxonContext; - - private ProductAttributesFinderInterface $attributesFinder; - public function __construct( - TaxonContextInterface $taxonContext, - ProductAttributesFinderInterface $attributesFinder + private TaxonContextInterface $taxonContext, + private ProductAttributesFinderInterface $attributesFinder ) { - $this->taxonContext = $taxonContext; - $this->attributesFinder = $attributesFinder; } public function getAttributes(): ?array diff --git a/src/Context/ProductOptionsContext.php b/src/Context/ProductOptionsContext.php index e0717ed5..b47db4b5 100644 --- a/src/Context/ProductOptionsContext.php +++ b/src/Context/ProductOptionsContext.php @@ -16,16 +16,10 @@ final class ProductOptionsContext implements ProductOptionsContextInterface { - private TaxonContextInterface $taxonContext; - - private ProductOptionsFinderInterface $optionsFinder; - public function __construct( - TaxonContextInterface $taxonContext, - ProductOptionsFinderInterface $optionsFinder + private TaxonContextInterface $taxonContext, + private ProductOptionsFinderInterface $optionsFinder ) { - $this->taxonContext = $taxonContext; - $this->optionsFinder = $optionsFinder; } public function getOptions(): ?array diff --git a/src/Context/ProductProductAttributesContext.php b/src/Context/ProductProductAttributesContext.php index 7e5185d8..53fd9858 100644 --- a/src/Context/ProductProductAttributesContext.php +++ b/src/Context/ProductProductAttributesContext.php @@ -16,18 +16,10 @@ final class ProductProductAttributesContext implements ProductAttributesContextInterface { - /** @var TaxonContextInterface */ - private $taxonContext; - - /** @var ProductAttributesFinderInterface */ - private $attributesFinder; - public function __construct( - TaxonContextInterface $taxonContext, - ProductAttributesFinderInterface $attributesFinder + private TaxonContextInterface $taxonContext, + private ProductAttributesFinderInterface $attributesFinder ) { - $this->taxonContext = $taxonContext; - $this->attributesFinder = $attributesFinder; } public function getAttributes(): ?array diff --git a/src/Context/TaxonContext.php b/src/Context/TaxonContext.php index e5ddd4e6..15037b6d 100644 --- a/src/Context/TaxonContext.php +++ b/src/Context/TaxonContext.php @@ -20,20 +20,11 @@ final class TaxonContext implements TaxonContextInterface { - private RequestStack $requestStack; - - private TaxonRepositoryInterface $taxonRepository; - - private LocaleContextInterface $localeContext; - public function __construct( - RequestStack $requestStack, - TaxonRepositoryInterface $taxonRepository, - LocaleContextInterface $localeContext + private RequestStack $requestStack, + private TaxonRepositoryInterface $taxonRepository, + private LocaleContextInterface $localeContext ) { - $this->requestStack = $requestStack; - $this->taxonRepository = $taxonRepository; - $this->localeContext = $localeContext; } public function getTaxon(): TaxonInterface diff --git a/src/Controller/Action/Api/ListProductsByPartialNameAction.php b/src/Controller/Action/Api/ListProductsByPartialNameAction.php index e2c71616..283fba10 100644 --- a/src/Controller/Action/Api/ListProductsByPartialNameAction.php +++ b/src/Controller/Action/Api/ListProductsByPartialNameAction.php @@ -23,24 +23,12 @@ final class ListProductsByPartialNameAction { - private NamedProductsFinderInterface $namedProductsFinder; - - private TransformerInterface $productSlugTransformer; - - private TransformerInterface $productChannelPriceTransformer; - - private TransformerInterface $productImageTransformer; - public function __construct( - NamedProductsFinderInterface $namedProductsFinder, - TransformerInterface $productSlugResolver, - TransformerInterface $productChannelPriceResolver, - TransformerInterface $productImageResolver + private NamedProductsFinderInterface $namedProductsFinder, + private TransformerInterface $productSlugTransformer, + private TransformerInterface $productChannelPriceTransformer, + private TransformerInterface $productImageTransformer ) { - $this->namedProductsFinder = $namedProductsFinder; - $this->productSlugTransformer = $productSlugResolver; - $this->productChannelPriceTransformer = $productChannelPriceResolver; - $this->productImageTransformer = $productImageResolver; } public function __invoke(Request $request): Response diff --git a/src/Controller/Action/Shop/AbstractSearchAction.php b/src/Controller/Action/Shop/AbstractSearchAction.php new file mode 100644 index 00000000..fbd0ceaa --- /dev/null +++ b/src/Controller/Action/Shop/AbstractSearchAction.php @@ -0,0 +1,53 @@ +getErrors(true) as $error) { + $errorOrigin = $error->getOrigin(); + $path = ($errorOrigin->getParent()->getPropertyPath() ?? '') . $errorOrigin->getPropertyPath(); + + $keys = explode('][', trim($path, '[]')); + + $dataRef = &$requestData; + foreach ($keys as $index => $key) { + if (isset($dataRef[$key])) { + if ($index === count($keys) - 1) { + unset($dataRef[$key]); + } else { + $dataRef = &$dataRef[$key]; + } + } + } + } + + return $requestData; + } +} diff --git a/src/Controller/Action/Shop/ListProductsAction.php b/src/Controller/Action/Shop/ListProductsAction.php deleted file mode 100644 index 5ff827f4..00000000 --- a/src/Controller/Action/Shop/ListProductsAction.php +++ /dev/null @@ -1,101 +0,0 @@ -formFactory = $formFactory; - $this->shopProductListDataHandler = $shopProductListDataHandler; - $this->shopProductsSortDataHandler = $shopProductsSortDataHandler; - $this->paginationDataHandler = $paginationDataHandler; - $this->shopProductsFinder = $shopProductsFinder; - $this->twig = $twig; - } - - public function __invoke(Request $request): Response - { - $form = $this->formFactory->create(ShopProductsFilterType::class); - $form->handleRequest($request); - $requestData = array_merge( - $form->getData(), - $request->query->all(), - ['slug' => $request->get('slug')] - ); - - if ($form->isSubmitted() && !$form->isValid()) { - $requestData = $this->clearInvalidEntries($form, $requestData); - } - - $data = array_merge( - $this->shopProductListDataHandler->retrieveData($requestData), - $this->shopProductsSortDataHandler->retrieveData($requestData), - $this->paginationDataHandler->retrieveData($requestData) - ); - - $template = $request->get('template'); - $products = $this->shopProductsFinder->find($data); - - return new Response($this->twig->render($template, [ - 'form' => $form->createView(), - 'products' => $products, - 'taxon' => $data['taxon'], - ])); - } - - private function clearInvalidEntries(FormInterface $form, array $requestData): array - { - $propertyAccessor = PropertyAccess::createPropertyAccessor(); - foreach ($form->getErrors(true) as $error) { - $errorOrigin = $error->getOrigin(); - $propertyAccessor->setValue( - $requestData, - ($errorOrigin->getParent()->getPropertyPath() ?? '') . $errorOrigin->getPropertyPath(), - '' - ); - } - - return $requestData; - } -} diff --git a/src/Controller/Action/Shop/SearchAction.php b/src/Controller/Action/Shop/SearchAction.php deleted file mode 100644 index 52c31756..00000000 --- a/src/Controller/Action/Shop/SearchAction.php +++ /dev/null @@ -1,95 +0,0 @@ -twig = $twig; - $this->finder = $finder; - $this->searchFormEventListener = $searchFormEventListener; - $this->facetRegistry = $facetRegistry; - $this->searchProductsQueryBuilder = $searchProductsQueryBuilder; - $this->paginationDataHandler = $paginationDataHandler; - } - - public function __invoke(Request $request): Response - { - $template = $request->get('template'); - $form = $this->searchFormEventListener->getForm(); - $form->handleRequest($request); - - $results = null; - if ($form->isSubmitted() && $form->isValid()) { - /** @var Search $search */ - $search = $form->getData(); - - $boolQuery = new Query\BoolQuery(); - $boolQuery->addMust( - $this->searchProductsQueryBuilder->buildQuery(['query' => $search->getBox()->getQuery()]) - ); - - if ($search->getFacets()) { - foreach ($search->getFacets() as $facetId => $selectedBuckets) { - if (!$selectedBuckets) { - continue; - } - $facet = $this->facetRegistry->getFacetById($facetId); - $boolQuery->addFilter($facet->getQuery($selectedBuckets)); - } - } - - $query = new Query($boolQuery); - - $results = $this->finder->findPaginated($query); - $paginationData = $this->paginationDataHandler->retrieveData($request->query->all()); - $results->setCurrentPage($paginationData[PaginationDataHandlerInterface::PAGE_INDEX]); - $results->setMaxPerPage($paginationData[PaginationDataHandlerInterface::LIMIT_INDEX]); - } - - return new Response($this->twig->render( - $template, - ['results' => $results, 'searchForm' => $form->createView()] - )); - } -} diff --git a/src/Controller/Action/Shop/SiteWideProductsSearchAction.php b/src/Controller/Action/Shop/SiteWideProductsSearchAction.php new file mode 100644 index 00000000..6b47f321 --- /dev/null +++ b/src/Controller/Action/Shop/SiteWideProductsSearchAction.php @@ -0,0 +1,53 @@ +get('template', '@BitBagSyliusElasticsearchPlugin/Shop/search.html.twig'); + $form = $this->formFactory->create(SearchType::class); + $form->handleRequest($request); + + if ($form->isSubmitted()) { + /** @var Search $search */ + $search = $form->getData(); + + $data = array_merge( + ['query' => $search->getBox()->getQuery()], + ['facets' => $search->getFacets()], + $this->dataHandler->retrieveData($request->query->all()), + ); + + if (!$form->isValid()) { + $data = $this->clearInvalidEntries($form, $data); + } + + $results = $this->finder->find($data); + } + + return new Response($this->twig->render( + $template, + [ + 'results' => $results ?? null, + 'searchForm' => $form->createView(), + ] + )); + } +} diff --git a/src/Controller/Action/Shop/TaxonProductsSearchAction.php b/src/Controller/Action/Shop/TaxonProductsSearchAction.php new file mode 100644 index 00000000..f1712e2a --- /dev/null +++ b/src/Controller/Action/Shop/TaxonProductsSearchAction.php @@ -0,0 +1,46 @@ +get('template'); + $form = $this->formFactory->create(ShopProductsFilterType::class); + $form->handleRequest($request); + + $requestData = array_merge( + $form->getData(), + $request->query->all(), + ['slug' => $request->get('slug')] + ); + + if ($form->isSubmitted() && !$form->isValid()) { + $requestData = $this->clearInvalidEntries($form, $requestData); + } + + $data = $this->dataHandler->retrieveData($requestData); + $products = $this->finder->find($data); + + return new Response($this->twig->render($template, [ + 'form' => $form->createView(), + 'products' => $products, + 'taxon' => $data['taxon'] ?? null, + ])); + } +} diff --git a/src/Controller/RequestDataHandler/PaginationDataHandler.php b/src/Controller/RequestDataHandler/PaginationDataHandler.php index 0a6c186d..32244247 100644 --- a/src/Controller/RequestDataHandler/PaginationDataHandler.php +++ b/src/Controller/RequestDataHandler/PaginationDataHandler.php @@ -14,11 +14,9 @@ final class PaginationDataHandler implements PaginationDataHandlerInterface { - private int $defaultLimit; - - public function __construct(int $defaultLimit) - { - $this->defaultLimit = $defaultLimit; + public function __construct( + private int $defaultLimit + ) { } public function retrieveData(array $requestData): array diff --git a/src/Controller/RequestDataHandler/ShopProductListDataHandler.php b/src/Controller/RequestDataHandler/ShopProductListDataHandler.php index 22a8b323..bef607de 100644 --- a/src/Controller/RequestDataHandler/ShopProductListDataHandler.php +++ b/src/Controller/RequestDataHandler/ShopProductListDataHandler.php @@ -20,32 +20,14 @@ final class ShopProductListDataHandler implements DataHandlerInterface { - private TaxonContextInterface $taxonContext; - - private ProductAttributesFinderInterface $attributesFinder; - - private string $namePropertyPrefix; - - private string $taxonsProperty; - - private string $optionPropertyPrefix; - - private string $attributePropertyPrefix; - public function __construct( - TaxonContextInterface $taxonContext, - ProductAttributesFinderInterface $attributesFinder, - string $namePropertyPrefix, - string $taxonsProperty, - string $optionPropertyPrefix, - string $attributePropertyPrefix + private TaxonContextInterface $taxonContext, + private ProductAttributesFinderInterface $attributesFinder, + private string $namePropertyPrefix, + private string $taxonsProperty, + private string $optionPropertyPrefix, + private string $attributePropertyPrefix ) { - $this->taxonContext = $taxonContext; - $this->attributesFinder = $attributesFinder; - $this->namePropertyPrefix = $namePropertyPrefix; - $this->taxonsProperty = $taxonsProperty; - $this->optionPropertyPrefix = $optionPropertyPrefix; - $this->attributePropertyPrefix = $attributePropertyPrefix; } public function retrieveData(array $requestData): array diff --git a/src/Controller/RequestDataHandler/ShopProductsSortDataHandler.php b/src/Controller/RequestDataHandler/ShopProductsSortDataHandler.php index 8d5d0234..1fd5c14c 100644 --- a/src/Controller/RequestDataHandler/ShopProductsSortDataHandler.php +++ b/src/Controller/RequestDataHandler/ShopProductsSortDataHandler.php @@ -12,54 +12,29 @@ namespace BitBag\SyliusElasticsearchPlugin\Controller\RequestDataHandler; -use BitBag\SyliusElasticsearchPlugin\Context\TaxonContextInterface; use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\ConcatedNameResolverInterface; use Sylius\Component\Channel\Context\ChannelContextInterface; use UnexpectedValueException; final class ShopProductsSortDataHandler implements SortDataHandlerInterface { - private ConcatedNameResolverInterface $channelPricingNameResolver; - - private ChannelContextInterface $channelContext; - - private TaxonContextInterface $taxonContext; - - private ConcatedNameResolverInterface $taxonPositionNameResolver; - - private string $soldUnitsProperty; - - private string $createdAtProperty; - - private string $pricePropertyPrefix; - public function __construct( - ConcatedNameResolverInterface $channelPricingNameResolver, - ChannelContextInterface $channelContext, - TaxonContextInterface $taxonContext, - ConcatedNameResolverInterface $taxonPositionNameResolver, - string $soldUnitsProperty, - string $createdAtProperty, - string $pricePropertyPrefix + private ConcatedNameResolverInterface $channelPricingNameResolver, + private ChannelContextInterface $channelContext, + private string $soldUnitsProperty, + private string $createdAtProperty, + private string $pricePropertyPrefix ) { - $this->channelPricingNameResolver = $channelPricingNameResolver; - $this->channelContext = $channelContext; - $this->taxonContext = $taxonContext; - $this->taxonPositionNameResolver = $taxonPositionNameResolver; - $this->soldUnitsProperty = $soldUnitsProperty; - $this->createdAtProperty = $createdAtProperty; - $this->pricePropertyPrefix = $pricePropertyPrefix; } public function retrieveData(array $requestData): array { $data = []; - $positionSortingProperty = $this->getPositionSortingProperty(); - $orderBy = $requestData[self::ORDER_BY_INDEX] ?? $positionSortingProperty; + $orderBy = $requestData[self::ORDER_BY_INDEX] ?? $this->createdAtProperty; $sort = $requestData[self::SORT_INDEX] ?? self::SORT_ASC_INDEX; - $availableSorters = [$positionSortingProperty, $this->soldUnitsProperty, $this->createdAtProperty, $this->pricePropertyPrefix]; + $availableSorters = [$this->soldUnitsProperty, $this->createdAtProperty, $this->pricePropertyPrefix]; $availableSorting = [self::SORT_ASC_INDEX, self::SORT_DESC_INDEX]; if (!in_array($orderBy, $availableSorters) || !in_array($sort, $availableSorting)) { @@ -75,11 +50,4 @@ public function retrieveData(array $requestData): array return $data; } - - private function getPositionSortingProperty(): string - { - $taxonCode = $this->taxonContext->getTaxon()->getCode(); - - return $this->taxonPositionNameResolver->resolvePropertyName($taxonCode); - } } diff --git a/src/Controller/RequestDataHandler/SiteWideDataHandler.php b/src/Controller/RequestDataHandler/SiteWideDataHandler.php new file mode 100644 index 00000000..026fb281 --- /dev/null +++ b/src/Controller/RequestDataHandler/SiteWideDataHandler.php @@ -0,0 +1,30 @@ +sortDataHandler->retrieveData($requestData), + $this->paginationDataHandler->retrieveData($requestData) + ); + } +} diff --git a/src/Controller/RequestDataHandler/TaxonDataHandler.php b/src/Controller/RequestDataHandler/TaxonDataHandler.php new file mode 100644 index 00000000..62c8de51 --- /dev/null +++ b/src/Controller/RequestDataHandler/TaxonDataHandler.php @@ -0,0 +1,32 @@ +shopProductListDataHandler->retrieveData($requestData), + $this->shopProductsSortDataHandler->retrieveData($requestData), + $this->paginationDataHandler->retrieveData($requestData) + ); + } +} diff --git a/src/Controller/Response/ItemsResponse.php b/src/Controller/Response/ItemsResponse.php index fc2aa49d..102509c3 100644 --- a/src/Controller/Response/ItemsResponse.php +++ b/src/Controller/Response/ItemsResponse.php @@ -16,12 +16,10 @@ final class ItemsResponse { - /** @var array|Item[] */ - private array $items; - - private function __construct(array $itemsList) - { - $this->items = $itemsList; + private function __construct( + /** @var $items array|Item[] */ + private array $items + ) { } public static function createEmpty(): self diff --git a/src/EventListener/OrderProductsListener.php b/src/EventListener/OrderProductsListener.php index 87c87a97..a15e8844 100644 --- a/src/EventListener/OrderProductsListener.php +++ b/src/EventListener/OrderProductsListener.php @@ -21,16 +21,10 @@ final class OrderProductsListener { - private ResourceRefresherInterface $resourceRefresher; - - private ObjectPersisterInterface $productPersister; - public function __construct( - ResourceRefresherInterface $resourceRefresher, - ObjectPersisterInterface $productPersister + private ResourceRefresherInterface $resourceRefresher, + private ObjectPersisterInterface $productPersister ) { - $this->resourceRefresher = $resourceRefresher; - $this->productPersister = $productPersister; } public function updateOrderProducts(GenericEvent $event): void diff --git a/src/EventListener/ProductTaxonIndexListener.php b/src/EventListener/ProductTaxonIndexListener.php index 0a4c1f3b..efd31e1d 100644 --- a/src/EventListener/ProductTaxonIndexListener.php +++ b/src/EventListener/ProductTaxonIndexListener.php @@ -18,16 +18,10 @@ final class ProductTaxonIndexListener { - private ResourceRefresherInterface $resourceRefresher; - - private ObjectPersisterInterface $objectPersister; - public function __construct( - ResourceRefresherInterface $resourceRefresher, - ObjectPersisterInterface $objectPersister + private ResourceRefresherInterface $resourceRefresher, + private ObjectPersisterInterface $objectPersister ) { - $this->resourceRefresher = $resourceRefresher; - $this->objectPersister = $objectPersister; } public function updateIndex(ProductTaxonInterface $productTaxon): void diff --git a/src/EventListener/ResourceIndexListener.php b/src/EventListener/ResourceIndexListener.php index 7fac2671..3cf5faca 100644 --- a/src/EventListener/ResourceIndexListener.php +++ b/src/EventListener/ResourceIndexListener.php @@ -24,24 +24,12 @@ final class ResourceIndexListener implements ResourceIndexListenerInterface { - private ResourceRefresherInterface $resourceRefresher; - - private array $persistersMap; - - private RepositoryInterface $attributeRepository; - - private RepositoryInterface $optionRepository; - public function __construct( - ResourceRefresherInterface $resourceRefresher, - array $persistersMap, - RepositoryInterface $attributeRepository, - RepositoryInterface $optionRepository + private ResourceRefresherInterface $resourceRefresher, + private array $persistersMap, + private RepositoryInterface $attributeRepository, + private RepositoryInterface $optionRepository ) { - $this->resourceRefresher = $resourceRefresher; - $this->persistersMap = $persistersMap; - $this->attributeRepository = $attributeRepository; - $this->optionRepository = $optionRepository; } public function updateIndex(GenericEvent $event): void diff --git a/src/Facet/AttributeFacet.php b/src/Facet/AttributeFacet.php index 8ef25509..f6fe4845 100644 --- a/src/Facet/AttributeFacet.php +++ b/src/Facet/AttributeFacet.php @@ -17,32 +17,17 @@ use Elastica\Aggregation\Terms; use Elastica\Query\AbstractQuery; use Elastica\Query\Terms as TermsQuery; -use RuntimeException; use function sprintf; use Sylius\Component\Attribute\Model\AttributeInterface; use Sylius\Component\Locale\Context\LocaleContextInterface; -use Sylius\Component\Resource\Repository\RepositoryInterface; final class AttributeFacet implements FacetInterface { - private ConcatedNameResolverInterface $attributeNameResolver; - - private RepositoryInterface $productAttributeRepository; - - private string $attributeCode; - - private LocaleContextInterface $localeContext; - public function __construct( - ConcatedNameResolverInterface $attributeNameResolver, - RepositoryInterface $productAttributeRepository, - string $attributeCode, - LocaleContextInterface $localeContext + private ConcatedNameResolverInterface $attributeNameResolver, + private AttributeInterface $attribute, + private LocaleContextInterface $localeContext ) { - $this->attributeNameResolver = $attributeNameResolver; - $this->productAttributeRepository = $productAttributeRepository; - $this->attributeCode = $attributeCode; - $this->localeContext = $localeContext; } public function getAggregation(): AbstractAggregation @@ -74,18 +59,13 @@ private function getFieldName(): string { return sprintf( '%s_%s.keyword', - $this->attributeNameResolver->resolvePropertyName($this->attributeCode), + $this->attributeNameResolver->resolvePropertyName($this->getProductAttribute()->getCode()), $this->localeContext->getLocaleCode() ); } private function getProductAttribute(): AttributeInterface { - $attribute = $this->productAttributeRepository->findOneBy(['code' => $this->attributeCode]); - if (!$attribute instanceof AttributeInterface) { - throw new RuntimeException(sprintf('Cannot find attribute with code "%s"', $this->attributeCode)); - } - - return $attribute; + return $this->attribute; } } diff --git a/src/Facet/AutoDiscoverRegistry.php b/src/Facet/AutoDiscoverRegistry.php new file mode 100644 index 00000000..5bc05c50 --- /dev/null +++ b/src/Facet/AutoDiscoverRegistry.php @@ -0,0 +1,92 @@ +autoRegister || [] !== $this->facets) { + return; + } + + $this->discoverAttributes(); + $this->discoverOptions(); + + foreach ($this->facets as $facetId => $facet) { + $this->registry->addFacet($facetId, $facet); + } + } + + private function discoverAttributes(): void + { + $attributes = $this->productAttributeRepository->findAllWithTranslations($this->localeContext->getLocaleCode()); + + /** @var AttributeInterface $attribute */ + foreach ($attributes as $attribute) { + $code = $attribute->getCode(); + + if (in_array($code, $this->excludedAttributes, true)) { + continue; + } + + $this->facets[$code] = new AttributeFacet( + $this->attributeNameResolver, + $attribute, + $this->localeContext, + ); + } + } + + private function discoverOptions(): void + { + $options = $this->productOptionRepository->findAllWithTranslations($this->localeContext->getLocaleCode()); + + /** @var ProductOptionInterface $option */ + foreach ($options as $option) { + $code = $option->getCode(); + + if (in_array($code, $this->excludedOptions, true)) { + continue; + } + + $this->facets[$code] = new OptionFacet( + $this->optionNameResolver, + $option, + ); + } + } +} diff --git a/src/Facet/AutoDiscoverRegistryInterface.php b/src/Facet/AutoDiscoverRegistryInterface.php new file mode 100644 index 00000000..fc817ad8 --- /dev/null +++ b/src/Facet/AutoDiscoverRegistryInterface.php @@ -0,0 +1,18 @@ +optionNameResolver = $optionNameResolver; - $this->productOptionRepository = $productOptionRepository; - $this->productOptionCode = $optionCode; } public function getAggregation(): AbstractAggregation @@ -61,16 +49,11 @@ public function getBucketLabel(array $bucket): string public function getLabel(): string { - $productOption = $this->productOptionRepository->findOneBy(['code' => $this->productOptionCode]); - if (!$productOption instanceof ProductOptionInterface) { - throw new RuntimeException(sprintf('Cannot find product option with code "%s"', $this->productOptionCode)); - } - - return $productOption->getName(); + return $this->productOption->getName(); } private function getFieldName(): string { - return $this->optionNameResolver->resolvePropertyName($this->productOptionCode) . '.keyword'; + return $this->optionNameResolver->resolvePropertyName($this->productOption->getCode()) . '.keyword'; } } diff --git a/src/Facet/PriceFacet.php b/src/Facet/PriceFacet.php index 18c416b9..0fddbc25 100644 --- a/src/Facet/PriceFacet.php +++ b/src/Facet/PriceFacet.php @@ -25,24 +25,12 @@ final class PriceFacet implements FacetInterface { public const FACET_ID = 'price'; - private ConcatedNameResolverInterface $channelPricingNameResolver; - - private MoneyFormatterInterface $moneyFormatter; - - private ShopperContextInterface $shopperContext; - - private int $interval; - public function __construct( - ConcatedNameResolverInterface $channelPricingNameResolver, - MoneyFormatterInterface $moneyFormatter, - ShopperContextInterface $shopperContext, - int $interval + private ConcatedNameResolverInterface $channelPricingNameResolver, + private MoneyFormatterInterface $moneyFormatter, + private ShopperContextInterface $shopperContext, + private int $interval ) { - $this->channelPricingNameResolver = $channelPricingNameResolver; - $this->moneyFormatter = $moneyFormatter; - $this->interval = $interval; - $this->shopperContext = $shopperContext; } public function getAggregation(): AbstractAggregation diff --git a/src/Facet/TaxonFacet.php b/src/Facet/TaxonFacet.php index 5ed0e6df..6aadc650 100644 --- a/src/Facet/TaxonFacet.php +++ b/src/Facet/TaxonFacet.php @@ -23,14 +23,10 @@ final class TaxonFacet implements FacetInterface { public const FACET_ID = 'taxon'; - private string $taxonsPropertyName; - - private TaxonRepositoryInterface $taxonRepository; - - public function __construct(TaxonRepositoryInterface $taxonRepository, string $taxonsPropertyName) - { - $this->taxonRepository = $taxonRepository; - $this->taxonsPropertyName = $taxonsPropertyName; + public function __construct( + private TaxonRepositoryInterface $taxonRepository, + private string $taxonsPropertyName + ) { } public function getAggregation(): AbstractAggregation diff --git a/src/Finder/NamedProductsFinder.php b/src/Finder/NamedProductsFinder.php index 41181f1e..94871c7e 100644 --- a/src/Finder/NamedProductsFinder.php +++ b/src/Finder/NamedProductsFinder.php @@ -17,22 +17,16 @@ final class NamedProductsFinder implements NamedProductsFinderInterface { - private QueryBuilderInterface $productsByPartialNameQueryBuilder; - - private FinderInterface $productsFinder; - public function __construct( - QueryBuilderInterface $productsByPartialNameQueryBuilder, - FinderInterface $productsFinder + private QueryBuilderInterface $queryBuilder, + private FinderInterface $productsFinder ) { - $this->productsByPartialNameQueryBuilder = $productsByPartialNameQueryBuilder; - $this->productsFinder = $productsFinder; } public function findByNamePart(string $namePart): ?array { - $data = ['name' => $namePart]; - $query = $this->productsByPartialNameQueryBuilder->buildQuery($data); + $data = ['query' => $namePart]; + $query = $this->queryBuilder->buildQuery($data); return $this->productsFinder->find($query); } diff --git a/src/Finder/ProductAttributesFinder.php b/src/Finder/ProductAttributesFinder.php index 12bd90ac..2c2b4c28 100644 --- a/src/Finder/ProductAttributesFinder.php +++ b/src/Finder/ProductAttributesFinder.php @@ -18,20 +18,11 @@ final class ProductAttributesFinder implements ProductAttributesFinderInterface { - private FinderInterface $attributesFinder; - - private QueryBuilderInterface $attributesByTaxonQueryBuilder; - - private string $taxonsProperty; - public function __construct( - FinderInterface $attributesFinder, - QueryBuilderInterface $attributesByTaxonQueryBuilder, - string $taxonsProperty + private FinderInterface $attributesFinder, + private QueryBuilderInterface $attributesByTaxonQueryBuilder, + private string $taxonsProperty ) { - $this->attributesFinder = $attributesFinder; - $this->attributesByTaxonQueryBuilder = $attributesByTaxonQueryBuilder; - $this->taxonsProperty = $taxonsProperty; } public function findByTaxon(TaxonInterface $taxon): ?array diff --git a/src/Finder/ProductOptionsFinder.php b/src/Finder/ProductOptionsFinder.php index e40126fe..bcf122ed 100644 --- a/src/Finder/ProductOptionsFinder.php +++ b/src/Finder/ProductOptionsFinder.php @@ -18,20 +18,11 @@ final class ProductOptionsFinder implements ProductOptionsFinderInterface { - private FinderInterface $optionsFinder; - - private QueryBuilderInterface $productOptionsByTaxonQueryBuilder; - - private string $taxonsProperty; - public function __construct( - FinderInterface $optionsFinder, - QueryBuilderInterface $productOptionsByTaxonQueryBuilder, - string $taxonsProperty + private FinderInterface $optionsFinder, + private QueryBuilderInterface $productOptionsByTaxonQueryBuilder, + private string $taxonsProperty ) { - $this->optionsFinder = $optionsFinder; - $this->productOptionsByTaxonQueryBuilder = $productOptionsByTaxonQueryBuilder; - $this->taxonsProperty = $taxonsProperty; } public function findByTaxon(TaxonInterface $taxon): ?array diff --git a/src/Finder/ShopProductsFinder.php b/src/Finder/ShopProductsFinder.php index b5cf40f8..bc3cafb4 100644 --- a/src/Finder/ShopProductsFinder.php +++ b/src/Finder/ShopProductsFinder.php @@ -22,43 +22,38 @@ final class ShopProductsFinder implements ShopProductsFinderInterface { - private QueryBuilderInterface $shopProductsQueryBuilder; - - private PaginatedFinderInterface $productFinder; - - /** @var RegistryInterface */ - private $facetRegistry; - public function __construct( - QueryBuilderInterface $shopProductsQueryBuilder, - PaginatedFinderInterface $productFinder, - RegistryInterface $facetRegistry + private QueryBuilderInterface $queryBuilder, + private PaginatedFinderInterface $productFinder, + private RegistryInterface $facetRegistry ) { - $this->shopProductsQueryBuilder = $shopProductsQueryBuilder; - $this->productFinder = $productFinder; - $this->facetRegistry = $facetRegistry; } public function find(array $data): Pagerfanta { - $boolQuery = $this->shopProductsQueryBuilder->buildQuery($data); + $boolQuery = $this->queryBuilder->buildQuery($data); - foreach ($data['facets'] as $facetId => $selectedBuckets) { - if (!$selectedBuckets) { - continue; - } + if (array_key_exists('facets', $data) && is_array($data['facets'])) { + foreach ($data['facets'] as $facetId => $selectedBuckets) { + if (!$selectedBuckets) { + continue; + } - $facet = $this->facetRegistry->getFacetById($facetId); - $boolQuery->addFilter($facet->getQuery($selectedBuckets)); + $facet = $this->facetRegistry->getFacetById($facetId); + $boolQuery->addFilter($facet->getQuery($selectedBuckets)); + } } $query = new Query($boolQuery); - $query->addSort($data[SortDataHandlerInterface::SORT_INDEX]); + if (array_key_exists(SortDataHandlerInterface::SORT_INDEX, $data)) { + $query->addSort($data[SortDataHandlerInterface::SORT_INDEX]); + } $products = $this->productFinder->findPaginated($query); if (null !== $data[PaginationDataHandlerInterface::LIMIT_INDEX]) { $products->setMaxPerPage($data[PaginationDataHandlerInterface::LIMIT_INDEX]); } + if (null !== $data[PaginationDataHandlerInterface::PAGE_INDEX]) { $products->setCurrentPage($data[PaginationDataHandlerInterface::PAGE_INDEX]); } diff --git a/src/Form/EventSubscriber/AddFacetsEventSubscriber.php b/src/Form/EventSubscriber/AddFacetsEventSubscriber.php new file mode 100644 index 00000000..1557b4a9 --- /dev/null +++ b/src/Form/EventSubscriber/AddFacetsEventSubscriber.php @@ -0,0 +1,64 @@ + 'addFacets', + ]; + } + + public function addFacets(FormEvent $event): void + { + $this->autoDiscoverRegistry->autoRegister(); + $adapter = $this->facetsResolver->resolveFacets($event, $this->namePropertyPrefix)->getAdapter(); + + $this->modifyForm($event->getForm(), $adapter); + } + + private function modifyForm(FormInterface $form, AdapterInterface $adapter): void + { + if (!$adapter instanceof FantaPaginatorAdapter) { + return; + } + + $form->add( + 'facets', + SearchFacetsType::class, + [ + 'facets' => $adapter->getAggregations(), + 'label' => false, + ] + ); + } +} diff --git a/src/Form/Resolver/FacetsResolver.php b/src/Form/Resolver/FacetsResolver.php new file mode 100644 index 00000000..3cdbeaa0 --- /dev/null +++ b/src/Form/Resolver/FacetsResolver.php @@ -0,0 +1,42 @@ +queryBuilder->getQuery($event); + + foreach ($this->facetRegistry->getFacets() as $facetId => $facet) { + $query->addAggregation($facet->getAggregation()->setName($facetId)); + } + + $query->setSize(0); + + return $this->finder->findPaginated($query); + } +} diff --git a/src/Form/Resolver/ProductsFilterFacetResolver.php b/src/Form/Resolver/ProductsFilterFacetResolver.php index 89976889..25aad1d0 100644 --- a/src/Form/Resolver/ProductsFilterFacetResolver.php +++ b/src/Form/Resolver/ProductsFilterFacetResolver.php @@ -18,20 +18,11 @@ final class ProductsFilterFacetResolver implements ProductsFilterFacetResolverInterface { - private TaxonFacetsQueryBuilderInterface $queryBuilder; - - private RegistryInterface $facetRegistry; - - private PaginatedFinderInterface $finder; - public function __construct( - TaxonFacetsQueryBuilderInterface $queryBuilder, - RegistryInterface $facetRegistry, - PaginatedFinderInterface $finder + private TaxonFacetsQueryBuilderInterface $queryBuilder, + private RegistryInterface $facetRegistry, + private PaginatedFinderInterface $finder ) { - $this->queryBuilder = $queryBuilder; - $this->facetRegistry = $facetRegistry; - $this->finder = $finder; } public function resolveFacets(FormEvent $event, string $namePropertyPrefix): Pagerfanta diff --git a/src/Form/Type/ChoiceMapper/ProductAttributesMapper.php b/src/Form/Type/ChoiceMapper/ProductAttributesMapper.php index f38321ce..ec57788b 100644 --- a/src/Form/Type/ChoiceMapper/ProductAttributesMapper.php +++ b/src/Form/Type/ChoiceMapper/ProductAttributesMapper.php @@ -22,29 +22,14 @@ final class ProductAttributesMapper implements ProductAttributesMapperInterface { - private ProductAttributeValueRepositoryInterface $productAttributeValueRepository; - - private LocaleContextInterface $localeContext; - - private StringFormatterInterface $stringFormatter; - - private TaxonContextInterface $taxonContext; - - /** @var AttributesMapperCollectorInterface[] */ - private iterable $attributeMapper; - public function __construct( - ProductAttributeValueRepositoryInterface $productAttributeValueRepository, - LocaleContextInterface $localeContext, - StringFormatterInterface $stringFormatter, - TaxonContextInterface $taxonContext, - iterable $attributeMapper + private ProductAttributeValueRepositoryInterface $productAttributeValueRepository, + private LocaleContextInterface $localeContext, + private StringFormatterInterface $stringFormatter, + private TaxonContextInterface $taxonContext, + /** @var $attributeMapper AttributesMapperCollectorInterface[] */ + private iterable $attributeMapper ) { - $this->productAttributeValueRepository = $productAttributeValueRepository; - $this->localeContext = $localeContext; - $this->stringFormatter = $stringFormatter; - $this->taxonContext = $taxonContext; - $this->attributeMapper = $attributeMapper; } public function mapToChoices(ProductAttributeInterface $productAttribute): array diff --git a/src/Form/Type/ChoiceMapper/ProductOptionsMapper.php b/src/Form/Type/ChoiceMapper/ProductOptionsMapper.php index 3b7f9199..2284c3f8 100644 --- a/src/Form/Type/ChoiceMapper/ProductOptionsMapper.php +++ b/src/Form/Type/ChoiceMapper/ProductOptionsMapper.php @@ -18,11 +18,9 @@ final class ProductOptionsMapper implements ProductOptionsMapperInterface { - private StringFormatterInterface $stringFormatter; - - public function __construct(StringFormatterInterface $stringFormatter) - { - $this->stringFormatter = $stringFormatter; + public function __construct( + private StringFormatterInterface $stringFormatter + ) { } public function mapToChoices(ProductOptionInterface $productOption): array diff --git a/src/Form/Type/PriceFilterType.php b/src/Form/Type/PriceFilterType.php index 1a7d7248..b3ab1131 100644 --- a/src/Form/Type/PriceFilterType.php +++ b/src/Form/Type/PriceFilterType.php @@ -26,16 +26,10 @@ final class PriceFilterType extends AbstractFilterType { public const MAXIMUM_PRICE_VALUE = 9999999999999999; - private PriceNameResolverInterface $priceNameResolver; - - private CurrencyContextInterface $currencyContext; - public function __construct( - PriceNameResolverInterface $priceNameResolver, - CurrencyContextInterface $currencyContext + private PriceNameResolverInterface $priceNameResolver, + private CurrencyContextInterface $currencyContext ) { - $this->priceNameResolver = $priceNameResolver; - $this->currencyContext = $currencyContext; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Type/ProductAttributesFilterType.php b/src/Form/Type/ProductAttributesFilterType.php index 5210a4cf..410a1c06 100644 --- a/src/Form/Type/ProductAttributesFilterType.php +++ b/src/Form/Type/ProductAttributesFilterType.php @@ -18,26 +18,15 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; +/** @deprecated */ final class ProductAttributesFilterType extends AbstractFilterType { - private ProductAttributesContextInterface $productAttributesContext; - - private ConcatedNameResolverInterface $attributeNameResolver; - - private ProductAttributesMapperInterface $productAttributesMapper; - - protected array $excludedAttributes; - public function __construct( - ProductAttributesContextInterface $productAttributesContext, - ConcatedNameResolverInterface $attributeNameResolver, - ProductAttributesMapperInterface $productAttributesMapper, - array $excludedAttributes + private ProductAttributesContextInterface $productAttributesContext, + private ConcatedNameResolverInterface $attributeNameResolver, + private ProductAttributesMapperInterface $productAttributesMapper, + private array $excludedAttributes ) { - $this->productAttributesContext = $productAttributesContext; - $this->attributeNameResolver = $attributeNameResolver; - $this->productAttributesMapper = $productAttributesMapper; - $this->excludedAttributes = $excludedAttributes; } public function buildForm(FormBuilderInterface $builder, array $attributes): void diff --git a/src/Form/Type/ProductOptionsFilterType.php b/src/Form/Type/ProductOptionsFilterType.php index 706323a4..ec93c8fc 100644 --- a/src/Form/Type/ProductOptionsFilterType.php +++ b/src/Form/Type/ProductOptionsFilterType.php @@ -18,22 +18,14 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; +/** @deprecated */ final class ProductOptionsFilterType extends AbstractFilterType { - private ProductOptionsContextInterface $productOptionsContext; - - private ConcatedNameResolverInterface $optionNameResolver; - - private ProductOptionsMapperInterface $productOptionsMapper; - public function __construct( - ProductOptionsContextInterface $productOptionsContext, - ConcatedNameResolverInterface $optionNameResolver, - ProductOptionsMapperInterface $productOptionsMapper + private ProductOptionsContextInterface $productOptionsContext, + private ConcatedNameResolverInterface $optionNameResolver, + private ProductOptionsMapperInterface $productOptionsMapper ) { - $this->productOptionsContext = $productOptionsContext; - $this->optionNameResolver = $optionNameResolver; - $this->productOptionsMapper = $productOptionsMapper; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Type/SearchFacetsType.php b/src/Form/Type/SearchFacetsType.php index 0e54ab21..1011dbfc 100644 --- a/src/Form/Type/SearchFacetsType.php +++ b/src/Form/Type/SearchFacetsType.php @@ -20,11 +20,9 @@ final class SearchFacetsType extends AbstractType { - private RegistryInterface $facetRegistry; - - public function __construct(RegistryInterface $facetRegistry) - { - $this->facetRegistry = $facetRegistry; + public function __construct( + private RegistryInterface $facetRegistry + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Type/SearchType.php b/src/Form/Type/SearchType.php index e6856c2c..bc381650 100644 --- a/src/Form/Type/SearchType.php +++ b/src/Form/Type/SearchType.php @@ -12,37 +12,20 @@ namespace BitBag\SyliusElasticsearchPlugin\Form\Type; -use BitBag\SyliusElasticsearchPlugin\Facet\RegistryInterface; +use BitBag\SyliusElasticsearchPlugin\Facet\AutoDiscoverRegistryInterface; +use BitBag\SyliusElasticsearchPlugin\Form\EventSubscriber\AddFacetsEventSubscriber; +use BitBag\SyliusElasticsearchPlugin\Form\Resolver\ProductsFilterFacetResolverInterface; use BitBag\SyliusElasticsearchPlugin\Model\Search; -use BitBag\SyliusElasticsearchPlugin\Model\SearchBox; -use BitBag\SyliusElasticsearchPlugin\QueryBuilder\QueryBuilderInterface; -use Elastica\Query; -use FOS\ElasticaBundle\Finder\PaginatedFinderInterface; -use FOS\ElasticaBundle\Paginator\FantaPaginatorAdapter; -use Pagerfanta\Adapter\AdapterInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolver; final class SearchType extends AbstractType { - private PaginatedFinderInterface $finder; - - private RegistryInterface $facetRegistry; - - private QueryBuilderInterface $searchProductsQueryBuilder; - public function __construct( - PaginatedFinderInterface $finder, - RegistryInterface $facetRegistry, - QueryBuilderInterface $searchProductsQueryBuilder + private AutoDiscoverRegistryInterface $autoDiscoverRegistry, + private ProductsFilterFacetResolverInterface $facetsResolver ) { - $this->finder = $finder; - $this->facetRegistry = $facetRegistry; - $this->searchProductsQueryBuilder = $searchProductsQueryBuilder; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -52,41 +35,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->setMethod('GET') ; - $formModifier = function (FormInterface $form, AdapterInterface $adapter) { - if (!$adapter instanceof FantaPaginatorAdapter) { - return; - } - - $form->add('facets', SearchFacetsType::class, ['facets' => $adapter->getAggregations(), 'label' => false]); - }; - - $builder - ->get('box') - ->addEventListener( - FormEvents::POST_SUBMIT, - function (FormEvent $event) use ($formModifier) { - /** @var SearchBox $data */ - $data = $event->getForm()->getData(); - - if (!$data->getQuery()) { - return; - } - - $query = new Query($this->searchProductsQueryBuilder->buildQuery(['query' => $data->getQuery()])); - - foreach ($this->facetRegistry->getFacets() as $facetId => $facet) { - $query->addAggregation($facet->getAggregation()->setName($facetId)); - } - $query->setSize(0); - - $results = $this->finder->findPaginated($query); - - if ($results->getAdapter()) { - $formModifier($event->getForm()->getParent(), $results->getAdapter()); - } - } - ) - ; + $builder->addEventSubscriber(new AddFacetsEventSubscriber( + $this->autoDiscoverRegistry, + $this->facetsResolver + )); } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Form/Type/ShopProductsFilterType.php b/src/Form/Type/ShopProductsFilterType.php index 66ddc4e6..02488136 100644 --- a/src/Form/Type/ShopProductsFilterType.php +++ b/src/Form/Type/ShopProductsFilterType.php @@ -12,62 +12,31 @@ namespace BitBag\SyliusElasticsearchPlugin\Form\Type; +use BitBag\SyliusElasticsearchPlugin\Facet\AutoDiscoverRegistryInterface; +use BitBag\SyliusElasticsearchPlugin\Form\EventSubscriber\AddFacetsEventSubscriber; use BitBag\SyliusElasticsearchPlugin\Form\Resolver\ProductsFilterFacetResolverInterface; -use FOS\ElasticaBundle\Paginator\FantaPaginatorAdapter; -use Pagerfanta\Adapter\AdapterInterface; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\FormInterface; final class ShopProductsFilterType extends AbstractFilterType { - private string $namePropertyPrefix; - - private ProductsFilterFacetResolverInterface $facetResolver; - public function __construct( - string $namePropertyPrefix, - ProductsFilterFacetResolverInterface $facetResolver + private AutoDiscoverRegistryInterface $autoDiscoverRegistry, + private string $namePropertyPrefix, + private ProductsFilterFacetResolverInterface $facetResolver ) { - $this->namePropertyPrefix = $namePropertyPrefix; - $this->facetResolver = $facetResolver; } public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add($this->namePropertyPrefix, NameFilterType::class) - ->add('options', ProductOptionsFilterType::class, ['required' => false, 'label' => false]) - ->add('attributes', ProductAttributesFilterType::class, ['required' => false, 'label' => false]) ->add('price', PriceFilterType::class, ['required' => false, 'label' => false]) ->setMethod('GET'); - $builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'addFacets']); - } - - public function addFacets(FormEvent $event): void - { - $facets = $this->facetResolver->resolveFacets($event, $this->namePropertyPrefix); - - if ($facets->getAdapter()) { - $this->modifyForm($event->getForm(), $facets->getAdapter()); - } - } - - private function modifyForm(FormInterface $form, AdapterInterface $adapter): void - { - if (!$adapter instanceof FantaPaginatorAdapter) { - return; - } - - $form->add( - 'facets', - SearchFacetsType::class, - [ - 'facets' => $adapter->getAggregations(), - 'label' => false, - ] - ); + $builder->addEventSubscriber(new AddFacetsEventSubscriber( + $this->autoDiscoverRegistry, + $this->facetResolver, + $this->namePropertyPrefix + )); } } diff --git a/src/PropertyBuilder/AttributeBuilder.php b/src/PropertyBuilder/AttributeBuilder.php index 05b13e2e..540113ba 100644 --- a/src/PropertyBuilder/AttributeBuilder.php +++ b/src/PropertyBuilder/AttributeBuilder.php @@ -30,16 +30,10 @@ final class AttributeBuilder extends AbstractBuilder public const DEFAULT_DATE_FORMAT = 'Y-m-d'; - private ConcatedNameResolverInterface $attributeNameResolver; - - private StringFormatterInterface $stringFormatter; - public function __construct( - ConcatedNameResolverInterface $attributeNameResolver, - StringFormatterInterface $stringFormatter + private ConcatedNameResolverInterface $attributeNameResolver, + private StringFormatterInterface $stringFormatter ) { - $this->attributeNameResolver = $attributeNameResolver; - $this->stringFormatter = $stringFormatter; } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/AttributeTaxonsBuilder.php b/src/PropertyBuilder/AttributeTaxonsBuilder.php index c4831e97..9f087a7c 100644 --- a/src/PropertyBuilder/AttributeTaxonsBuilder.php +++ b/src/PropertyBuilder/AttributeTaxonsBuilder.php @@ -20,24 +20,12 @@ final class AttributeTaxonsBuilder extends AbstractBuilder { - protected TaxonRepositoryInterface $taxonRepository; - - private string $taxonsProperty; - - private array $excludedAttributes; - - private bool $includeAllDescendants; - public function __construct( - TaxonRepositoryInterface $taxonRepository, - string $taxonsProperty, - bool $includeAllDescendants, - array $excludedAttributes = [] + private TaxonRepositoryInterface $taxonRepository, + private string $taxonsProperty, + private bool $includeAllDescendants, + private array $excludedAttributes = [] ) { - $this->taxonRepository = $taxonRepository; - $this->taxonsProperty = $taxonsProperty; - $this->includeAllDescendants = $includeAllDescendants; - $this->excludedAttributes = $excludedAttributes; } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ChannelPricingBuilder.php b/src/PropertyBuilder/ChannelPricingBuilder.php index 1e5e0799..0c98d253 100644 --- a/src/PropertyBuilder/ChannelPricingBuilder.php +++ b/src/PropertyBuilder/ChannelPricingBuilder.php @@ -20,11 +20,9 @@ final class ChannelPricingBuilder extends AbstractBuilder { - private ConcatedNameResolverInterface $channelPricingNameResolver; - - public function __construct(ConcatedNameResolverInterface $channelPricingNameResolver) - { - $this->channelPricingNameResolver = $channelPricingNameResolver; + public function __construct( + private ConcatedNameResolverInterface $channelPricingNameResolver + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ChannelsBuilder.php b/src/PropertyBuilder/ChannelsBuilder.php index aaff5c04..63633f81 100644 --- a/src/PropertyBuilder/ChannelsBuilder.php +++ b/src/PropertyBuilder/ChannelsBuilder.php @@ -18,11 +18,9 @@ final class ChannelsBuilder extends AbstractBuilder { - private string $channelsProperty; - - public function __construct(string $channelsProperty) - { - $this->channelsProperty = $channelsProperty; + public function __construct( + private string $channelsProperty + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/Mapper/ProductTaxonsMapper.php b/src/PropertyBuilder/Mapper/ProductTaxonsMapper.php index 0c485ca2..4674b57c 100644 --- a/src/PropertyBuilder/Mapper/ProductTaxonsMapper.php +++ b/src/PropertyBuilder/Mapper/ProductTaxonsMapper.php @@ -16,11 +16,9 @@ final class ProductTaxonsMapper implements ProductTaxonsMapperInterface { - private bool $includeAllDescendants; - - public function __construct(bool $includeAllDescendants) - { - $this->includeAllDescendants = $includeAllDescendants; + public function __construct( + private bool $includeAllDescendants + ) { } public function mapToUniqueCodes(ProductInterface $product): array diff --git a/src/PropertyBuilder/OptionBuilder.php b/src/PropertyBuilder/OptionBuilder.php index fe3d0f40..acc6f6ce 100644 --- a/src/PropertyBuilder/OptionBuilder.php +++ b/src/PropertyBuilder/OptionBuilder.php @@ -20,16 +20,10 @@ final class OptionBuilder extends AbstractBuilder { - private ConcatedNameResolverInterface $optionNameResolver; - - private StringFormatterInterface $stringFormatter; - public function __construct( - ConcatedNameResolverInterface $optionNameResolver, - StringFormatterInterface $stringFormatter + private ConcatedNameResolverInterface $optionNameResolver, + private StringFormatterInterface $stringFormatter ) { - $this->optionNameResolver = $optionNameResolver; - $this->stringFormatter = $stringFormatter; } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/OptionTaxonsBuilder.php b/src/PropertyBuilder/OptionTaxonsBuilder.php index 08a7a711..5cf992ae 100644 --- a/src/PropertyBuilder/OptionTaxonsBuilder.php +++ b/src/PropertyBuilder/OptionTaxonsBuilder.php @@ -21,28 +21,13 @@ final class OptionTaxonsBuilder extends AbstractBuilder { - private RepositoryInterface $productOptionValueRepository; - - private ProductVariantRepositoryInterface $productVariantRepository; - - private ProductTaxonsMapperInterface $productTaxonsMapper; - - private string $taxonsProperty; - - private array $excludedOptions; - public function __construct( - RepositoryInterface $productOptionValueRepository, - ProductVariantRepositoryInterface $productVariantRepository, - ProductTaxonsMapperInterface $productTaxonsMapper, - string $taxonsProperty, - array $excludedOptions = [] + private RepositoryInterface $productOptionValueRepository, + private ProductVariantRepositoryInterface $productVariantRepository, + private ProductTaxonsMapperInterface $productTaxonsMapper, + private string $taxonsProperty, + private array $excludedOptions = [] ) { - $this->productOptionValueRepository = $productOptionValueRepository; - $this->productVariantRepository = $productVariantRepository; - $this->productTaxonsMapper = $productTaxonsMapper; - $this->taxonsProperty = $taxonsProperty; - $this->excludedOptions = $excludedOptions; } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ProductCreatedAtPropertyBuilder.php b/src/PropertyBuilder/ProductCreatedAtPropertyBuilder.php index 9b2f896e..cf844413 100644 --- a/src/PropertyBuilder/ProductCreatedAtPropertyBuilder.php +++ b/src/PropertyBuilder/ProductCreatedAtPropertyBuilder.php @@ -18,11 +18,9 @@ final class ProductCreatedAtPropertyBuilder extends AbstractBuilder { - private string $createdAtProperty; - - public function __construct(string $createdAtProperty) - { - $this->createdAtProperty = $createdAtProperty; + public function __construct( + private string $createdAtProperty + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ProductDescriptionBuilder.php b/src/PropertyBuilder/ProductDescriptionBuilder.php index 95fb57d4..37d92e09 100644 --- a/src/PropertyBuilder/ProductDescriptionBuilder.php +++ b/src/PropertyBuilder/ProductDescriptionBuilder.php @@ -20,11 +20,9 @@ final class ProductDescriptionBuilder extends AbstractBuilder { - private ConcatedNameResolverInterface $productDescriptionNameResolver; - - public function __construct(ConcatedNameResolverInterface $productDescriptionNameResolver) - { - $this->productDescriptionNameResolver = $productDescriptionNameResolver; + public function __construct( + private ConcatedNameResolverInterface $productDescriptionNameResolver + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ProductNameBuilder.php b/src/PropertyBuilder/ProductNameBuilder.php index cf99af68..97328762 100644 --- a/src/PropertyBuilder/ProductNameBuilder.php +++ b/src/PropertyBuilder/ProductNameBuilder.php @@ -20,11 +20,9 @@ final class ProductNameBuilder extends AbstractBuilder { - private ConcatedNameResolverInterface $productNameNameResolver; - - public function __construct(ConcatedNameResolverInterface $productNameNameResolver) - { - $this->productNameNameResolver = $productNameNameResolver; + public function __construct( + private ConcatedNameResolverInterface $productNameNameResolver + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ProductShortDescriptionBuilder.php b/src/PropertyBuilder/ProductShortDescriptionBuilder.php index ef9f0ba4..158be691 100644 --- a/src/PropertyBuilder/ProductShortDescriptionBuilder.php +++ b/src/PropertyBuilder/ProductShortDescriptionBuilder.php @@ -20,11 +20,9 @@ final class ProductShortDescriptionBuilder extends AbstractBuilder { - private ConcatedNameResolverInterface $productShortDescriptionNameResolver; - - public function __construct(ConcatedNameResolverInterface $productShortDescriptionNameResolver) - { - $this->productShortDescriptionNameResolver = $productShortDescriptionNameResolver; + public function __construct( + private ConcatedNameResolverInterface $productShortDescriptionNameResolver + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ProductTaxonPositionPropertyBuilder.php b/src/PropertyBuilder/ProductTaxonPositionPropertyBuilder.php index 3a5985aa..b88fd649 100644 --- a/src/PropertyBuilder/ProductTaxonPositionPropertyBuilder.php +++ b/src/PropertyBuilder/ProductTaxonPositionPropertyBuilder.php @@ -19,11 +19,9 @@ final class ProductTaxonPositionPropertyBuilder extends AbstractBuilder { - private ConcatedNameResolverInterface $taxonPositionNameResolver; - - public function __construct(ConcatedNameResolverInterface $taxonPositionNameResolver) - { - $this->taxonPositionNameResolver = $taxonPositionNameResolver; + public function __construct( + private ConcatedNameResolverInterface $taxonPositionNameResolver + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/ProductTaxonsBuilder.php b/src/PropertyBuilder/ProductTaxonsBuilder.php index 19a146a1..daa1c57a 100644 --- a/src/PropertyBuilder/ProductTaxonsBuilder.php +++ b/src/PropertyBuilder/ProductTaxonsBuilder.php @@ -19,14 +19,10 @@ final class ProductTaxonsBuilder extends AbstractBuilder { - private ProductTaxonsMapperInterface $productTaxonsMapper; - - private string $taxonsProperty; - - public function __construct(ProductTaxonsMapperInterface $productTaxonsMapper, string $taxonsProperty) - { - $this->productTaxonsMapper = $productTaxonsMapper; - $this->taxonsProperty = $taxonsProperty; + public function __construct( + private ProductTaxonsMapperInterface $productTaxonsMapper, + private string $taxonsProperty + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyBuilder/SoldUnitsPropertyBuilder.php b/src/PropertyBuilder/SoldUnitsPropertyBuilder.php index a631d584..b74682d1 100644 --- a/src/PropertyBuilder/SoldUnitsPropertyBuilder.php +++ b/src/PropertyBuilder/SoldUnitsPropertyBuilder.php @@ -19,14 +19,10 @@ final class SoldUnitsPropertyBuilder extends AbstractBuilder { - private OrderItemRepositoryInterface $orderItemRepository; - - private string $soldUnitsProperty; - - public function __construct(OrderItemRepositoryInterface $orderItemRepository, string $soldUnitsProperty) - { - $this->orderItemRepository = $orderItemRepository; - $this->soldUnitsProperty = $soldUnitsProperty; + public function __construct( + private OrderItemRepositoryInterface $orderItemRepository, + private string $soldUnitsProperty + ) { } public function consumeEvent(PostTransformEvent $event): void diff --git a/src/PropertyNameResolver/ConcatedNameResolver.php b/src/PropertyNameResolver/ConcatedNameResolver.php index 324236d3..cc588ca4 100644 --- a/src/PropertyNameResolver/ConcatedNameResolver.php +++ b/src/PropertyNameResolver/ConcatedNameResolver.php @@ -14,11 +14,9 @@ final class ConcatedNameResolver implements ConcatedNameResolverInterface { - private string $propertyPrefix; - - public function __construct(string $propertyPrefix) - { - $this->propertyPrefix = $propertyPrefix; + public function __construct( + private string $propertyPrefix + ) { } public function resolvePropertyName(string $suffix): string diff --git a/src/PropertyNameResolver/PriceNameResolver.php b/src/PropertyNameResolver/PriceNameResolver.php index 1ea2df4c..4a4d9491 100644 --- a/src/PropertyNameResolver/PriceNameResolver.php +++ b/src/PropertyNameResolver/PriceNameResolver.php @@ -14,11 +14,9 @@ final class PriceNameResolver implements PriceNameResolverInterface { - private string $pricePropertyPrefix; - - public function __construct(string $pricePropertyPrefix) - { - $this->pricePropertyPrefix = $pricePropertyPrefix; + public function __construct( + private string $pricePropertyPrefix + ) { } public function resolveMinPriceName(): string diff --git a/src/QueryBuilder/ContainsNameQueryBuilder.php b/src/QueryBuilder/ContainsNameQueryBuilder.php index a7741343..2054c8c9 100644 --- a/src/QueryBuilder/ContainsNameQueryBuilder.php +++ b/src/QueryBuilder/ContainsNameQueryBuilder.php @@ -12,44 +12,39 @@ namespace BitBag\SyliusElasticsearchPlugin\QueryBuilder; -use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\ConcatedNameResolverInterface; +use BitBag\SyliusElasticsearchPlugin\PropertyNameResolver\SearchPropertyNameResolverRegistryInterface; use Elastica\Query\AbstractQuery; -use Elastica\Query\MatchQuery; +use Elastica\Query\MultiMatch; use Sylius\Component\Locale\Context\LocaleContextInterface; final class ContainsNameQueryBuilder implements QueryBuilderInterface { - private LocaleContextInterface $localeContext; - - private ConcatedNameResolverInterface $productNameNameResolver; - - private string $namePropertyPrefix; - public function __construct( - LocaleContextInterface $localeContext, - ConcatedNameResolverInterface $productNameNameResolver, - string $namePropertyPrefix + private LocaleContextInterface $localeContext, + private SearchPropertyNameResolverRegistryInterface $searchProperyNameResolverRegistry, + private string $fuzziness = 'AUTO' ) { - $this->localeContext = $localeContext; - $this->productNameNameResolver = $productNameNameResolver; - $this->namePropertyPrefix = $namePropertyPrefix; } public function buildQuery(array $data): ?AbstractQuery { $localeCode = $this->localeContext->getLocaleCode(); - $propertyName = $this->productNameNameResolver->resolvePropertyName($localeCode); + $query = $data['name'] ?? $data['query'] ?? null; - if (!$name = $data[$this->namePropertyPrefix]) { + if (null === $query || '' === $query) { return null; } - $nameQuery = new MatchQuery(); + $fields = []; + foreach ($this->searchProperyNameResolverRegistry->getPropertyNameResolvers() as $propertyNameResolver) { + $fields[] = $propertyNameResolver->resolvePropertyName($localeCode); + } - $nameQuery->setFieldQuery($propertyName, $name); - $nameQuery->setFieldFuzziness($propertyName, 2); - $nameQuery->setFieldMinimumShouldMatch($propertyName, 2); + $multiMatch = new MultiMatch(); + $multiMatch->setQuery($query); + $multiMatch->setFuzziness($this->fuzziness); + $multiMatch->setFields($fields); - return $nameQuery; + return $multiMatch; } } diff --git a/src/QueryBuilder/FormQueryBuilder/SiteWideFacetsQueryBuilder.php b/src/QueryBuilder/FormQueryBuilder/SiteWideFacetsQueryBuilder.php new file mode 100644 index 00000000..eb563524 --- /dev/null +++ b/src/QueryBuilder/FormQueryBuilder/SiteWideFacetsQueryBuilder.php @@ -0,0 +1,48 @@ +getData(); + + $boolQuery = $this->queryBuilder->buildQuery([ + 'query' => $data['box']['query'] ?? '', + ]); + + foreach ($data['facets'] ?? [] as $facetId => $selectedBuckets) { + if (!$selectedBuckets) { + continue; + } + + $facet = $this->facetRegistry->getFacetById($facetId); + $boolQuery->addFilter($facet->getQuery($selectedBuckets)); + } + + return new Query($boolQuery); + } +} diff --git a/src/QueryBuilder/FormQueryBuilder/SiteWideFacetsQueryBuilderInterface.php b/src/QueryBuilder/FormQueryBuilder/SiteWideFacetsQueryBuilderInterface.php new file mode 100644 index 00000000..09a9f142 --- /dev/null +++ b/src/QueryBuilder/FormQueryBuilder/SiteWideFacetsQueryBuilderInterface.php @@ -0,0 +1,20 @@ +shopProductListDataHandler = $shopProductListDataHandler; - $this->searchProductsQueryBuilder = $searchProductsQueryBuilder; } public function getQuery(FormEvent $event, string $namePropertyPrefix): Query @@ -38,6 +34,17 @@ public function getQuery(FormEvent $event, string $namePropertyPrefix): Query $data = $this->shopProductListDataHandler->retrieveData($eventData); - return new Query($this->searchProductsQueryBuilder->buildQuery($data)); + $boolQuery = $this->searchProductsQueryBuilder->buildQuery($data); + + foreach ($data['facets'] ?? [] as $facetId => $selectedBuckets) { + if (!$selectedBuckets) { + continue; + } + + $facet = $this->facetRegistry->getFacetById($facetId); + $boolQuery->addFilter($facet->getQuery($selectedBuckets)); + } + + return new Query($boolQuery); } } diff --git a/src/QueryBuilder/HasAttributesQueryBuilder.php b/src/QueryBuilder/HasAttributesQueryBuilder.php index a964866e..ed739593 100644 --- a/src/QueryBuilder/HasAttributesQueryBuilder.php +++ b/src/QueryBuilder/HasAttributesQueryBuilder.php @@ -19,21 +19,12 @@ final class HasAttributesQueryBuilder implements QueryBuilderInterface { - private LocaleContextInterface $localeContext; - - private ProductAttributeRepositoryInterface $productAttributeRepository; - - /** @var AttributesQueryBuilderCollectorInterface[] */ - private iterable $attributeDriver; - public function __construct( - LocaleContextInterface $localeContext, - ProductAttributeRepositoryInterface $productAttributeRepository, - iterable $attributeDriver + private LocaleContextInterface $localeContext, + private ProductAttributeRepositoryInterface $productAttributeRepository, + /** @var AttributesQueryBuilderCollectorInterface[] */ + private iterable $attributeDriver ) { - $this->localeContext = $localeContext; - $this->productAttributeRepository = $productAttributeRepository; - $this->attributeDriver = $attributeDriver; } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/HasChannelQueryBuilder.php b/src/QueryBuilder/HasChannelQueryBuilder.php index 9ef4571b..99d39eb3 100644 --- a/src/QueryBuilder/HasChannelQueryBuilder.php +++ b/src/QueryBuilder/HasChannelQueryBuilder.php @@ -18,14 +18,10 @@ final class HasChannelQueryBuilder implements QueryBuilderInterface { - private ChannelContextInterface $channelContext; - - private string $channelsProperty; - - public function __construct(ChannelContextInterface $channelContext, string $channelsProperty) - { - $this->channelContext = $channelContext; - $this->channelsProperty = $channelsProperty; + public function __construct( + private ChannelContextInterface $channelContext, + private string $channelsProperty + ) { } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/HasPriceBetweenQueryBuilder.php b/src/QueryBuilder/HasPriceBetweenQueryBuilder.php index f6ba7230..4efdb367 100644 --- a/src/QueryBuilder/HasPriceBetweenQueryBuilder.php +++ b/src/QueryBuilder/HasPriceBetweenQueryBuilder.php @@ -25,28 +25,13 @@ final class HasPriceBetweenQueryBuilder implements QueryBuilderInterface { - private ConcatedNameResolverInterface $channelPricingNameResolver; - - private PriceNameResolverInterface $priceNameResolver; - - private ChannelContextInterface $channelContext; - - private CurrencyContextInterface $currencyContext; - - private CurrencyConverterInterface $currencyConverter; - public function __construct( - PriceNameResolverInterface $priceNameResolver, - ConcatedNameResolverInterface $channelPricingNameResolver, - ChannelContextInterface $channelContext, - CurrencyContextInterface $currencyContext, - CurrencyConverterInterface $currencyConverter + private PriceNameResolverInterface $priceNameResolver, + private ConcatedNameResolverInterface $channelPricingNameResolver, + private ChannelContextInterface $channelContext, + private CurrencyContextInterface $currencyContext, + private CurrencyConverterInterface $currencyConverter ) { - $this->channelPricingNameResolver = $channelPricingNameResolver; - $this->priceNameResolver = $priceNameResolver; - $this->channelContext = $channelContext; - $this->currencyContext = $currencyContext; - $this->currencyConverter = $currencyConverter; } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/HasTaxonQueryBuilder.php b/src/QueryBuilder/HasTaxonQueryBuilder.php index 0464940b..cfa5da54 100644 --- a/src/QueryBuilder/HasTaxonQueryBuilder.php +++ b/src/QueryBuilder/HasTaxonQueryBuilder.php @@ -17,11 +17,9 @@ final class HasTaxonQueryBuilder implements QueryBuilderInterface { - private string $taxonsProperty; - - public function __construct(string $taxonsProperty) - { - $this->taxonsProperty = $taxonsProperty; + public function __construct( + private string $taxonsProperty + ) { } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/IsEnabledQueryBuilder.php b/src/QueryBuilder/IsEnabledQueryBuilder.php index 55c7f007..b0c615be 100644 --- a/src/QueryBuilder/IsEnabledQueryBuilder.php +++ b/src/QueryBuilder/IsEnabledQueryBuilder.php @@ -17,11 +17,9 @@ final class IsEnabledQueryBuilder implements QueryBuilderInterface { - private string $enabledProperty; - - public function __construct(string $enabledProperty) - { - $this->enabledProperty = $enabledProperty; + public function __construct( + private string $enabledProperty + ) { } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/ProductAttributesByTaxonQueryBuilder.php b/src/QueryBuilder/ProductAttributesByTaxonQueryBuilder.php index 5674b4dc..afde00fc 100644 --- a/src/QueryBuilder/ProductAttributesByTaxonQueryBuilder.php +++ b/src/QueryBuilder/ProductAttributesByTaxonQueryBuilder.php @@ -17,11 +17,9 @@ final class ProductAttributesByTaxonQueryBuilder implements QueryBuilderInterface { - private QueryBuilderInterface $hasTaxonQueryBuilder; - - public function __construct(QueryBuilderInterface $hasTaxonQueryBuilder) - { - $this->hasTaxonQueryBuilder = $hasTaxonQueryBuilder; + public function __construct( + private QueryBuilderInterface $hasTaxonQueryBuilder + ) { } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/ProductOptionsByTaxonQueryBuilder.php b/src/QueryBuilder/ProductOptionsByTaxonQueryBuilder.php index 43016dd7..42c87cec 100644 --- a/src/QueryBuilder/ProductOptionsByTaxonQueryBuilder.php +++ b/src/QueryBuilder/ProductOptionsByTaxonQueryBuilder.php @@ -17,11 +17,9 @@ final class ProductOptionsByTaxonQueryBuilder implements QueryBuilderInterface { - private QueryBuilderInterface $hasTaxonQueryBuilder; - - public function __construct(QueryBuilderInterface $hasTaxonQueryBuilder) - { - $this->hasTaxonQueryBuilder = $hasTaxonQueryBuilder; + public function __construct( + private QueryBuilderInterface $hasTaxonQueryBuilder + ) { } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/ProductsByPartialNameQueryBuilder.php b/src/QueryBuilder/ProductsByPartialNameQueryBuilder.php index 0ffa2b58..9cb82d99 100644 --- a/src/QueryBuilder/ProductsByPartialNameQueryBuilder.php +++ b/src/QueryBuilder/ProductsByPartialNameQueryBuilder.php @@ -17,11 +17,9 @@ final class ProductsByPartialNameQueryBuilder implements QueryBuilderInterface { - private QueryBuilderInterface $containsNameQueryBuilder; - - public function __construct(QueryBuilderInterface $containsNameQueryBuilder) - { - $this->containsNameQueryBuilder = $containsNameQueryBuilder; + public function __construct( + private QueryBuilderInterface $containsNameQueryBuilder + ) { } public function buildQuery(array $data): ?AbstractQuery diff --git a/src/QueryBuilder/SearchProductsQueryBuilder.php b/src/QueryBuilder/SearchProductsQueryBuilder.php deleted file mode 100644 index 20e88f0b..00000000 --- a/src/QueryBuilder/SearchProductsQueryBuilder.php +++ /dev/null @@ -1,86 +0,0 @@ -searchProperyNameResolverRegistry = $searchProperyNameResolverRegistry; - $this->localeContext = $localeContext; - $this->isEnabledQueryBuilder = $isEnabledQueryBuilder; - $this->hasChannelQueryBuilder = $hasChannelQueryBuilder; - $this->fuzziness = $fuzziness; - } - - public function buildQuery(array $data): ?AbstractQuery - { - if (!array_key_exists(self::QUERY_KEY, $data)) { - throw new \RuntimeException( - sprintf( - 'Could not build search products query because there\'s no "query" key in provided data. ' . - 'Got the following keys: %s', - implode(', ', array_keys($data)) - ) - ); - } - $query = $data[self::QUERY_KEY]; - if (!is_string($query)) { - throw new \RuntimeException( - sprintf( - 'Could not build search products query because the provided "query" is expected to be a string ' . - 'but "%s" is given.', - is_object($query) ? get_class($query) : gettype($query) - ) - ); - } - - $multiMatch = new MultiMatch(); - $multiMatch->setQuery($query); - $multiMatch->setFuzziness($this->fuzziness); - $fields = []; - foreach ($this->searchProperyNameResolverRegistry->getPropertyNameResolvers() as $propertyNameResolver) { - $fields[] = $propertyNameResolver->resolvePropertyName($this->localeContext->getLocaleCode()); - } - $multiMatch->setFields($fields); - $bool = new BoolQuery(); - $bool->addMust($multiMatch); - $bool->addFilter($this->isEnabledQueryBuilder->buildQuery([])); - $bool->addFilter($this->hasChannelQueryBuilder->buildQuery([])); - - return $bool; - } -} diff --git a/src/QueryBuilder/SiteWideProductsQueryBuilder.php b/src/QueryBuilder/SiteWideProductsQueryBuilder.php new file mode 100644 index 00000000..0b83d2c5 --- /dev/null +++ b/src/QueryBuilder/SiteWideProductsQueryBuilder.php @@ -0,0 +1,46 @@ +addMust($this->isEnabledQueryBuilder->buildQuery([])); + $boolQuery->addMust($this->hasChannelQueryBuilder->buildQuery([])); + + $nameQuery = $this->containsNameQueryBuilder->buildQuery($data); + $this->addMustIfNotNull($nameQuery, $boolQuery); + + return $boolQuery; + } + + private function addMustIfNotNull(?AbstractQuery $query, BoolQuery $boolQuery): void + { + if (null !== $query) { + $boolQuery->addMust($query); + } + } +} diff --git a/src/QueryBuilder/ShopProductsQueryBuilder.php b/src/QueryBuilder/TaxonProductsQueryBuilder.php similarity index 58% rename from src/QueryBuilder/ShopProductsQueryBuilder.php rename to src/QueryBuilder/TaxonProductsQueryBuilder.php index c3ad979d..d81132d9 100644 --- a/src/QueryBuilder/ShopProductsQueryBuilder.php +++ b/src/QueryBuilder/TaxonProductsQueryBuilder.php @@ -15,46 +15,19 @@ use Elastica\Query\AbstractQuery; use Elastica\Query\BoolQuery; -final class ShopProductsQueryBuilder implements QueryBuilderInterface +final class TaxonProductsQueryBuilder implements QueryBuilderInterface { - private QueryBuilderInterface $isEnabledQueryBuilder; - - private QueryBuilderInterface $hasChannelQueryBuilder; - - private QueryBuilderInterface $containsNameQueryBuilder; - - private QueryBuilderInterface $hasTaxonQueryBuilder; - - private QueryBuilderInterface $hasOptionsQueryBuilder; - - private QueryBuilderInterface $hasAttributesQueryBuilder; - - private QueryBuilderInterface $hasPriceBetweenQueryBuilder; - - private string $optionPropertyPrefix; - - private string $attributePropertyPrefix; - public function __construct( - QueryBuilderInterface $isEnabledQueryBuilder, - QueryBuilderInterface $hasChannelQueryBuilder, - QueryBuilderInterface $containsNameQueryBuilder, - QueryBuilderInterface $hasTaxonQueryBuilder, - QueryBuilderInterface $hasOptionsQueryBuilder, - QueryBuilderInterface $hasAttributesQueryBuilder, - QueryBuilderInterface $hasPriceBetweenQueryBuilder, - string $optionPropertyPrefix, - string $attributePropertyPrefix + private QueryBuilderInterface $isEnabledQueryBuilder, + private QueryBuilderInterface $hasChannelQueryBuilder, + private QueryBuilderInterface $containsNameQueryBuilder, + private QueryBuilderInterface $hasTaxonQueryBuilder, + private QueryBuilderInterface $hasOptionsQueryBuilder, + private QueryBuilderInterface $hasAttributesQueryBuilder, + private QueryBuilderInterface $hasPriceBetweenQueryBuilder, + private string $optionPropertyPrefix, + private string $attributePropertyPrefix ) { - $this->isEnabledQueryBuilder = $isEnabledQueryBuilder; - $this->hasChannelQueryBuilder = $hasChannelQueryBuilder; - $this->containsNameQueryBuilder = $containsNameQueryBuilder; - $this->hasTaxonQueryBuilder = $hasTaxonQueryBuilder; - $this->hasOptionsQueryBuilder = $hasOptionsQueryBuilder; - $this->hasAttributesQueryBuilder = $hasAttributesQueryBuilder; - $this->hasPriceBetweenQueryBuilder = $hasPriceBetweenQueryBuilder; - $this->optionPropertyPrefix = $optionPropertyPrefix; - $this->attributePropertyPrefix = $attributePropertyPrefix; } public function buildQuery(array $data): AbstractQuery diff --git a/src/Repository/OrderItemRepository.php b/src/Repository/OrderItemRepository.php index b59657df..5ff4dfe1 100644 --- a/src/Repository/OrderItemRepository.php +++ b/src/Repository/OrderItemRepository.php @@ -16,13 +16,11 @@ use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\ProductVariantInterface; -final class OrderItemRepository implements OrderItemRepositoryInterface +class OrderItemRepository implements OrderItemRepositoryInterface { - private OrderItemRepositoryInterface|EntityRepository $baseOrderItemRepository; - - public function __construct(EntityRepository $baseOrderItemRepository) - { - $this->baseOrderItemRepository = $baseOrderItemRepository; + public function __construct( + private OrderItemRepositoryInterface|EntityRepository $baseOrderItemRepository + ) { } public function countByVariant(ProductVariantInterface $variant, array $orderStates = []): int diff --git a/src/Repository/ProductAttributeRepository.php b/src/Repository/ProductAttributeRepository.php index e5f2f3bd..2255c193 100644 --- a/src/Repository/ProductAttributeRepository.php +++ b/src/Repository/ProductAttributeRepository.php @@ -12,16 +12,14 @@ namespace BitBag\SyliusElasticsearchPlugin\Repository; -use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\ORM\QueryBuilder; use Sylius\Component\Resource\Repository\RepositoryInterface; class ProductAttributeRepository implements ProductAttributeRepositoryInterface { - private RepositoryInterface $productAttributeRepository; - - public function __construct(RepositoryInterface $productAttributeRepository) - { - $this->productAttributeRepository = $productAttributeRepository; + public function __construct( + private RepositoryInterface $productAttributeRepository + ) { } public function getAttributeTypeByName(string $attributeName): string @@ -38,4 +36,24 @@ public function getAttributeTypeByName(string $attributeName): string return $result['type']; } + + public function findAllWithTranslations(?string $locale): array + { + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = $this->productAttributeRepository->createQueryBuilder('o'); + + if (null !== $locale) { + $queryBuilder + ->addSelect('translation') + ->leftJoin('o.translations', 'translation', 'ot') + ->andWhere('translation.locale = :locale') + ->setParameter('locale', $locale) + ; + } + + return $queryBuilder + ->getQuery() + ->getResult() + ; + } } diff --git a/src/Repository/ProductAttributeRepositoryInterface.php b/src/Repository/ProductAttributeRepositoryInterface.php index c3e2f0e9..46a20578 100644 --- a/src/Repository/ProductAttributeRepositoryInterface.php +++ b/src/Repository/ProductAttributeRepositoryInterface.php @@ -15,4 +15,6 @@ interface ProductAttributeRepositoryInterface { public function getAttributeTypeByName(string $attributeName): string; + + public function findAllWithTranslations(?string $locale): array; } diff --git a/src/Repository/ProductAttributeValueRepository.php b/src/Repository/ProductAttributeValueRepository.php index a852563b..4e0730ad 100644 --- a/src/Repository/ProductAttributeValueRepository.php +++ b/src/Repository/ProductAttributeValueRepository.php @@ -16,18 +16,12 @@ use Sylius\Component\Product\Repository\ProductAttributeValueRepositoryInterface as BaseAttributeValueRepositoryInterface; use Sylius\Component\Taxonomy\Model\Taxon; -final class ProductAttributeValueRepository implements ProductAttributeValueRepositoryInterface +class ProductAttributeValueRepository implements ProductAttributeValueRepositoryInterface { - private BaseAttributeValueRepositoryInterface $baseAttributeValueRepository; - - private bool $includeAllDescendants; - public function __construct( - BaseAttributeValueRepositoryInterface $baseAttributeValueRepository, - bool $includeAllDescendants + private BaseAttributeValueRepositoryInterface $baseAttributeValueRepository, + private bool $includeAllDescendants ) { - $this->baseAttributeValueRepository = $baseAttributeValueRepository; - $this->includeAllDescendants = $includeAllDescendants; } public function getUniqueAttributeValues(AttributeInterface $productAttribute, Taxon $taxon): array diff --git a/src/Repository/ProductOptionRepository.php b/src/Repository/ProductOptionRepository.php new file mode 100644 index 00000000..df513c81 --- /dev/null +++ b/src/Repository/ProductOptionRepository.php @@ -0,0 +1,44 @@ +productOptionRepository->createQueryBuilder('o'); + + if (null !== $locale) { + $queryBuilder + ->addSelect('translation') + ->leftJoin('o.translations', 'translation', 'ot') + ->andWhere('translation.locale = :locale') + ->setParameter('locale', $locale) + ; + } + + return $queryBuilder + ->getQuery() + ->getResult() + ; + } +} diff --git a/src/Repository/ProductOptionRepositoryInterface.php b/src/Repository/ProductOptionRepositoryInterface.php new file mode 100644 index 00000000..e5daf281 --- /dev/null +++ b/src/Repository/ProductOptionRepositoryInterface.php @@ -0,0 +1,18 @@ +baseProductVariantRepository = $baseProductVariantRepository; + public function __construct( + private BaseProductVariantRepositoryInterface|EntityRepository $baseProductVariantRepository + ) { } public function findOneByOptionValue(ProductOptionValueInterface $productOptionValue): ?ProductVariantInterface diff --git a/src/Repository/TaxonRepository.php b/src/Repository/TaxonRepository.php index 95b26b2e..07de826b 100644 --- a/src/Repository/TaxonRepository.php +++ b/src/Repository/TaxonRepository.php @@ -18,26 +18,14 @@ use Sylius\Component\Core\Repository\ProductRepositoryInterface; use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface as BaseTaxonRepositoryInterface; -final class TaxonRepository implements TaxonRepositoryInterface +class TaxonRepository implements TaxonRepositoryInterface { - private BaseTaxonRepositoryInterface|EntityRepository $baseTaxonRepository; - - private ProductRepositoryInterface|EntityRepository $productRepository; - - private string $productTaxonEntityClass; - - private string $productAttributeEntityClass; - public function __construct( - BaseTaxonRepositoryInterface $baseTaxonRepository, - ProductRepositoryInterface $productRepository, - string $productTaxonEntityClass, - string $productAttributeEntityClass + private BaseTaxonRepositoryInterface|EntityRepository $baseTaxonRepository, + private ProductRepositoryInterface|EntityRepository $productRepository, + private string $productTaxonEntityClass, + private string $productAttributeEntityClass ) { - $this->baseTaxonRepository = $baseTaxonRepository; - $this->productRepository = $productRepository; - $this->productTaxonEntityClass = $productTaxonEntityClass; - $this->productAttributeEntityClass = $productAttributeEntityClass; } public function getTaxonsByAttributeViaProduct(AttributeInterface $attribute): array diff --git a/src/Resources/assets/shop/entry.js b/src/Resources/assets/shop/entry.js index 6306ee0a..9b7cbfb8 100644 --- a/src/Resources/assets/shop/entry.js +++ b/src/Resources/assets/shop/entry.js @@ -1,2 +1,2 @@ import './scss/main.scss' -import './js/' \ No newline at end of file +import './js/' diff --git a/src/Resources/assets/shop/js/elasticSearchCheckboxChangeSubmit.js b/src/Resources/assets/shop/js/elasticSearchCheckboxChangeSubmit.js new file mode 100644 index 00000000..67d55306 --- /dev/null +++ b/src/Resources/assets/shop/js/elasticSearchCheckboxChangeSubmit.js @@ -0,0 +1,26 @@ +export default class ElasticSearchCheckboxChangeSubmit { + constructor(config = {}) { + this.config = config; + this.defaultConfig = { + checkboxSelector: '.bitbag-sylius-elasticsearch-plugin-facets-form input[type="checkbox"]', + formSelector: 'form', + }; + this.finalConfig = {...this.defaultConfig, ...config}; + } + + init() { + const checkboxes = document.querySelectorAll(this.finalConfig.checkboxSelector); + checkboxes.forEach((checkbox) => { + checkbox.addEventListener('change', () => { + this._submitForm(checkbox); + }); + }); + } + + _submitForm(checkbox) { + const form = checkbox.closest(this.finalConfig.formSelector); + if (form) { + form.submit(); + } + } +} diff --git a/src/Resources/assets/shop/js/index.js b/src/Resources/assets/shop/js/index.js index a472edda..ac83f309 100644 --- a/src/Resources/assets/shop/js/index.js +++ b/src/Resources/assets/shop/js/index.js @@ -1 +1,2 @@ -import './initAutocomleate'; \ No newline at end of file +import './initAutocomplete'; +import './initCheckboxChangeSubmit'; diff --git a/src/Resources/assets/shop/js/initAutocomleate.js b/src/Resources/assets/shop/js/initAutocomplete.js similarity index 63% rename from src/Resources/assets/shop/js/initAutocomleate.js rename to src/Resources/assets/shop/js/initAutocomplete.js index 8e4e324a..02db7b33 100644 --- a/src/Resources/assets/shop/js/initAutocomleate.js +++ b/src/Resources/assets/shop/js/initAutocomplete.js @@ -1,3 +1,3 @@ import ElasticSearchAutocomplete from './elasticSearchAutocomplete'; -new ElasticSearchAutocomplete().init(); \ No newline at end of file +new ElasticSearchAutocomplete().init(); diff --git a/src/Resources/assets/shop/js/initCheckboxChangeSubmit.js b/src/Resources/assets/shop/js/initCheckboxChangeSubmit.js new file mode 100644 index 00000000..c844fc53 --- /dev/null +++ b/src/Resources/assets/shop/js/initCheckboxChangeSubmit.js @@ -0,0 +1,5 @@ +import ElasticSearchCheckboxChangeSubmit from './elasticSearchCheckboxChangeSubmit'; + +document.addEventListener('DOMContentLoaded', () => { + new ElasticSearchCheckboxChangeSubmit().init(); +}); diff --git a/src/Resources/config/config.yml b/src/Resources/config/config.yml index 66f93c72..c0d0bda5 100644 --- a/src/Resources/config/config.yml +++ b/src/Resources/config/config.yml @@ -15,6 +15,9 @@ parameters: bitbag_es_pagination_available_page_limits: [9, 18, 36] bitbag_es_pagination_default_limit: 9 bitbag_es_fuzziness: AUTO + bitbag_es_facets_auto_discover: true + bitbag_es_excluded_facet_attributes: [] + bitbag_es_excluded_facet_options: [] fos_elastica: clients: @@ -25,3 +28,10 @@ fos_elastica: twig: globals: bitbag_es_pagination_available_page_limits: "%bitbag_es_pagination_available_page_limits%" + +sylius_ui: + events: + sylius.shop.layout.header.content: + blocks: + bitbag_es_search_form: + template: "@BitBagSyliusElasticsearchPlugin/Shop/Menu/_searchForm.html.twig" diff --git a/src/Resources/config/routing.yml b/src/Resources/config/routing.yml index 0da5a2ea..c4103d97 100644 --- a/src/Resources/config/routing.yml +++ b/src/Resources/config/routing.yml @@ -1,7 +1,7 @@ bitbag_sylius_elasticsearch_plugin_shop_list_products: path: /{_locale}/products-list/{slug} defaults: - _controller: bitbag_sylius_elasticsearch_plugin.controller.action.shop.list_products + _controller: bitbag_sylius_elasticsearch_plugin.controller.action.shop.taxon_products_search template: "@BitBagSyliusElasticsearchPlugin/Shop/Product/index.html.twig" requirements: slug: .+ @@ -16,5 +16,5 @@ bitbag_sylius_elasticsearch_plugin_shop_auto_complete_product_name: bitbag_sylius_elasticsearch_plugin_shop_search: path: /{_locale}/search defaults: - _controller: bitbag_sylius_elasticsearch_plugin.controller.action.shop.search + _controller: bitbag_sylius_elasticsearch_plugin.controller.action.shop.site_wide_products_search template: "@BitBagSyliusElasticsearchPlugin/Shop/search.html.twig" diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 485f508d..e97395c3 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -8,10 +8,6 @@ - - - - @@ -39,5 +35,9 @@ + + + + diff --git a/src/Resources/config/services/block_event_listener.xml b/src/Resources/config/services/block_event_listener.xml deleted file mode 100644 index edd43f37..00000000 --- a/src/Resources/config/services/block_event_listener.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - @BitBagSyliusElasticsearchPlugin/Shop/Menu/_searchForm.html.twig - - - - - - diff --git a/src/Resources/config/services/controller.xml b/src/Resources/config/services/controller.xml index 3ea26d9d..ea545f6b 100644 --- a/src/Resources/config/services/controller.xml +++ b/src/Resources/config/services/controller.xml @@ -2,16 +2,6 @@ - - - - - - - - - - @@ -28,11 +18,20 @@ - - %bitbag_es_shop_product_sold_units% %bitbag_es_shop_product_created_at% %bitbag_es_shop_product_price_property_prefix% + + + + + + + + + + + diff --git a/src/Resources/config/services/controller/shop.xml b/src/Resources/config/services/controller/shop.xml index d7c88264..b976de70 100644 --- a/src/Resources/config/services/controller/shop.xml +++ b/src/Resources/config/services/controller/shop.xml @@ -2,23 +2,19 @@ - + - - - + - + + + + - - - - - diff --git a/src/Resources/config/services/facet.xml b/src/Resources/config/services/facet.xml index 680a5fde..5497fe13 100644 --- a/src/Resources/config/services/facet.xml +++ b/src/Resources/config/services/facet.xml @@ -14,8 +14,19 @@ %bitbag_es_shop_product_taxons_property% - - - + + + + %bitbag_es_facets_auto_discover% + + + + + + + %bitbag_es_excluded_facet_attributes% + %bitbag_es_excluded_facet_options% + + diff --git a/src/Resources/config/services/finder.xml b/src/Resources/config/services/finder.xml index 5d64df3b..445526f5 100644 --- a/src/Resources/config/services/finder.xml +++ b/src/Resources/config/services/finder.xml @@ -3,14 +3,20 @@ - + - + - + + + + + + + diff --git a/src/Resources/config/services/form.xml b/src/Resources/config/services/form.xml index 2814ea28..145438b4 100644 --- a/src/Resources/config/services/form.xml +++ b/src/Resources/config/services/form.xml @@ -28,6 +28,7 @@ + %bitbag_es_shop_name_property_prefix% @@ -46,9 +47,8 @@ - - - + + @@ -71,7 +71,13 @@ - + + + + + + + diff --git a/src/Resources/config/services/query_builder.xml b/src/Resources/config/services/query_builder.xml index b77beff7..b64f5591 100644 --- a/src/Resources/config/services/query_builder.xml +++ b/src/Resources/config/services/query_builder.xml @@ -13,8 +13,7 @@ - - %bitbag_es_shop_name_property_prefix% + @@ -69,7 +68,7 @@ - + @@ -81,17 +80,22 @@ %bitbag_es_shop_attribute_property_prefix% - - - + + %bitbag_es_fuzziness% - + + + + + + + diff --git a/src/Resources/config/services/twig.xml b/src/Resources/config/services/twig.xml new file mode 100644 index 00000000..dd6abc5f --- /dev/null +++ b/src/Resources/config/services/twig.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/Resources/views/Shop/Menu/_searchForm.html.twig b/src/Resources/views/Shop/Menu/_searchForm.html.twig index faefa7d1..bf5cb90f 100644 --- a/src/Resources/views/Shop/Menu/_searchForm.html.twig +++ b/src/Resources/views/Shop/Menu/_searchForm.html.twig @@ -2,10 +2,10 @@ diff --git a/src/Resources/views/Shop/Product/Index/_filterForm.html.twig b/src/Resources/views/Shop/Product/Index/_filterForm.html.twig index 46d3a626..5c6a20c9 100644 --- a/src/Resources/views/Shop/Product/Index/_filterForm.html.twig +++ b/src/Resources/views/Shop/Product/Index/_filterForm.html.twig @@ -1,15 +1,14 @@ -
- {{ form_row(form.options) }} -
-
- {{ form_row(form.attributes) }} +
+ {% for facet in form.facets %} +
+ {{ form_label(facet) }} + {{ form_widget(facet) }} +
+ {% endfor %}
{{ form_row(form.price) }}
-
- {{ form_row(form.facets) }} -
diff --git a/src/Resources/views/Shop/Product/index.html.twig b/src/Resources/views/Shop/Product/index.html.twig index 4326e82e..d3471145 100644 --- a/src/Resources/views/Shop/Product/index.html.twig +++ b/src/Resources/views/Shop/Product/index.html.twig @@ -55,4 +55,4 @@
{{ form_end(form, {'render_rest': false}) }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Resources/views/Shop/search.html.twig b/src/Resources/views/Shop/search.html.twig index daf0a5e1..372fb25c 100644 --- a/src/Resources/views/Shop/search.html.twig +++ b/src/Resources/views/Shop/search.html.twig @@ -19,9 +19,16 @@
{{ form_row(searchForm.box) }}
-
- {{ form_rest(searchForm) }} -
+ {% if searchForm.facets is defined %} +
+ {% for facet in searchForm.facets %} +
+ {{ form_label(facet) }} + {{ form_widget(facet, sylius_test_form_attribute(facet.vars.id)) }} +
+ {% endfor %} +
+ {% endif %}
+
+
+
+ {% include '@BitBagSyliusElasticsearchPlugin/Shop/Product/Index/_sorting.html.twig' %} + {% include '@BitBagSyliusElasticsearchPlugin/Shop/Product/Index/_pagination.html.twig' %} +
+
+
+ {% if results|length > 0 %}
{% for product in results %} diff --git a/src/Transformer/Product/ChannelPricingTransformer.php b/src/Transformer/Product/ChannelPricingTransformer.php index 36a41736..084da818 100644 --- a/src/Transformer/Product/ChannelPricingTransformer.php +++ b/src/Transformer/Product/ChannelPricingTransformer.php @@ -23,24 +23,12 @@ final class ChannelPricingTransformer implements TransformerInterface { - private ChannelContextInterface $channelContext; - - private LocaleContextInterface $localeContext; - - private ProductVariantResolverInterface $productVariantResolver; - - private MoneyFormatterInterface $moneyFormatter; - public function __construct( - ChannelContextInterface $channelContext, - LocaleContextInterface $localeContext, - ProductVariantResolverInterface $productVariantResolver, - MoneyFormatterInterface $moneyFormatter + private ChannelContextInterface $channelContext, + private LocaleContextInterface $localeContext, + private ProductVariantResolverInterface $productVariantResolver, + private MoneyFormatterInterface $moneyFormatter ) { - $this->channelContext = $channelContext; - $this->localeContext = $localeContext; - $this->productVariantResolver = $productVariantResolver; - $this->moneyFormatter = $moneyFormatter; } public function transform(ProductInterface $product): ?string diff --git a/src/Transformer/Product/ImageTransformer.php b/src/Transformer/Product/ImageTransformer.php index 160602ca..2cfdfbe7 100644 --- a/src/Transformer/Product/ImageTransformer.php +++ b/src/Transformer/Product/ImageTransformer.php @@ -22,14 +22,10 @@ final class ImageTransformer implements TransformerInterface private const SYLIUS_THUMBNAIL_FILTER = 'sylius_shop_product_thumbnail'; - private FilterService $imagineFilter; - - private string $imagesPath; - - public function __construct(FilterService $imagineFilter, string $imagesPath = '/media/image/') - { - $this->imagineFilter = $imagineFilter; - $this->imagesPath = $imagesPath; + public function __construct( + private FilterService $imagineFilter, + private string $imagesPath = '/media/image/' + ) { } public function transform(ProductInterface $product): ?string diff --git a/src/Transformer/Product/SlugTransformer.php b/src/Transformer/Product/SlugTransformer.php index efe92ede..7cf2531f 100644 --- a/src/Transformer/Product/SlugTransformer.php +++ b/src/Transformer/Product/SlugTransformer.php @@ -17,11 +17,9 @@ final class SlugTransformer implements TransformerInterface { - private RouterInterface $router; - - public function __construct(RouterInterface $router) - { - $this->router = $router; + public function __construct( + private RouterInterface $router + ) { } public function transform(ProductInterface $product): ?string diff --git a/src/Twig/ContextProvider/SearchFormProvider.php b/src/Twig/ContextProvider/SearchFormProvider.php new file mode 100644 index 00000000..144d05c4 --- /dev/null +++ b/src/Twig/ContextProvider/SearchFormProvider.php @@ -0,0 +1,44 @@ +formFactory->create(SearchType::class); + $templateContext['form'] = $form->createView(); + + return $templateContext; + } + + public function supports(TemplateBlock $templateBlock): bool + { + return self::EVENT_NAME === $templateBlock->getEventName() + && self::BLOCK_NAME === $templateBlock->getName(); + } +} diff --git a/tests/Application/.env b/tests/Application/.env index 69696635..ab32b2fe 100755 --- a/tests/Application/.env +++ b/tests/Application/.env @@ -36,8 +36,4 @@ MAILER_URL=smtp://localhost MESSENGER_TRANSPORT_DSN=sync:// ###< symfony/messenger ### -###> sylius/elasticsearch-plugin ### -BITBAG_ES_INDEX_PREFIX= -BITBAG_ES_HOST=localhost -BITBAG_ES_PORT=9200 -###< sylius/elasticsearch-plugin ### +ELASTICSEARCH_URL=http://localhost:9200/ diff --git a/tests/Application/config/packages/fos_elastica.yaml b/tests/Application/config/packages/fos_elastica.yaml new file mode 100644 index 00000000..4bccf12f --- /dev/null +++ b/tests/Application/config/packages/fos_elastica.yaml @@ -0,0 +1,3 @@ +fos_elastica: + clients: + default: { url: '%env(ELASTICSEARCH_URL)%' } diff --git a/tests/Application/config/routes/sylius_shop.yaml b/tests/Application/config/routes/sylius_shop.yaml index fae46cbf..c791c659 100644 --- a/tests/Application/config/routes/sylius_shop.yaml +++ b/tests/Application/config/routes/sylius_shop.yaml @@ -12,3 +12,13 @@ sylius_shop_default_locale: methods: [GET] defaults: _controller: sylius.controller.shop.locale_switch::switchAction + +redirect_sylius_shop_product_index: + path: /{_locale}/taxons/{slug} + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction + defaults: + route: bitbag_sylius_elasticsearch_plugin_shop_list_products + permanent: true + requirements: + _locale: ^[a-z]{2}(?:_[A-Z]{2})?$ + slug: .+ diff --git a/tests/Application/config/services.yaml b/tests/Application/config/services.yaml index af568d03..102c22e6 100755 --- a/tests/Application/config/services.yaml +++ b/tests/Application/config/services.yaml @@ -5,38 +5,6 @@ parameters: bitbag_es_shop_price_facet_interval: 1000000 services: - - bitbag_sylius_elasticsearch_plugin.facet.attribute.car_type: - class: BitBag\SyliusElasticsearchPlugin\Facet\AttributeFacet - arguments: - - '@bitbag_sylius_elasticsearch_plugin.property_name_resolver.attribute' - - '@sylius.repository.product_attribute' - - Car_Type - - '@sylius.context.locale' - - bitbag_sylius_elasticsearch_plugin.facet.attribute.motorbike_type: - class: BitBag\SyliusElasticsearchPlugin\Facet\AttributeFacet - arguments: - - '@bitbag_sylius_elasticsearch_plugin.property_name_resolver.attribute' - - '@sylius.repository.product_attribute' - - Motorbike_Type - - '@sylius.context.locale' - - bitbag_sylius_elasticsearch_plugin.facet.attribute.color: - class: BitBag\SyliusElasticsearchPlugin\Facet\AttributeFacet - arguments: - - '@bitbag_sylius_elasticsearch_plugin.property_name_resolver.attribute' - - '@sylius.repository.product_attribute' - - Color - - '@sylius.context.locale' - - bitbag_sylius_elasticsearch_plugin.facet.option.supply: - class: BitBag\SyliusElasticsearchPlugin\Facet\OptionFacet - arguments: - - '@bitbag_sylius_elasticsearch_plugin.property_name_resolver.option' - - '@sylius.repository.product_option' - - SUPPLY - bitbag_sylius_elasticsearch_plugin.facet.registry: class: BitBag\SyliusElasticsearchPlugin\Facet\Registry calls: @@ -48,27 +16,3 @@ services: arguments: - taxon - '@bitbag_sylius_elasticsearch_plugin.facet.taxon' - - method: addFacet - arguments: - - attribute_car_type - - '@bitbag_sylius_elasticsearch_plugin.facet.attribute.car_type' - - method: addFacet - arguments: - - attribute_motorbike_type - - '@bitbag_sylius_elasticsearch_plugin.facet.attribute.motorbike_type' - - method: addFacet - arguments: - - attribute_color - - '@bitbag_sylius_elasticsearch_plugin.facet.attribute.color' - - method: addFacet - arguments: - - option_supply - - '@bitbag_sylius_elasticsearch_plugin.facet.option.supply' - - bitbag_sylius_elasticsearch_plugin.facet.taxon_registry: - class: BitBag\SyliusElasticsearchPlugin\Facet\Registry - calls: - - method: addFacet - arguments: - - price - - '@bitbag_sylius_elasticsearch_plugin.facet.price' diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Product/Show/_breadcrumb.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Product/Show/_breadcrumb.html.twig new file mode 100644 index 00000000..5d45521f --- /dev/null +++ b/tests/Application/templates/bundles/SyliusShopBundle/Product/Show/_breadcrumb.html.twig @@ -0,0 +1,21 @@ + diff --git a/tests/Behat/Context/Api/Shop/ProductContext.php b/tests/Behat/Context/Api/Shop/ProductContext.php index 171f83b8..8a4b4154 100644 --- a/tests/Behat/Context/Api/Shop/ProductContext.php +++ b/tests/Behat/Context/Api/Shop/ProductContext.php @@ -18,16 +18,10 @@ final class ProductContext implements Context { - /** @var AbstractBrowser */ - private $client; - - /** @var RouterInterface */ - private $router; - - public function __construct(AbstractBrowser $client, RouterInterface $router) - { - $this->client = $client; - $this->router = $router; + public function __construct( + private AbstractBrowser $client, + private RouterInterface $router + ) { } /** diff --git a/tests/Behat/Context/Setup/ElasticsearchContext.php b/tests/Behat/Context/Setup/ElasticsearchContext.php index c6faac82..cea8408a 100644 --- a/tests/Behat/Context/Setup/ElasticsearchContext.php +++ b/tests/Behat/Context/Setup/ElasticsearchContext.php @@ -15,12 +15,9 @@ final class ElasticsearchContext implements Context { - /** @var Populate */ - private $populate; - - public function __construct(Populate $populate) - { - $this->populate = $populate; + public function __construct( + private Populate $populate + ) { } /** diff --git a/tests/Behat/Context/Setup/ProductAttributeContext.php b/tests/Behat/Context/Setup/ProductAttributeContext.php index 9c9b5ab5..1a1aa776 100644 --- a/tests/Behat/Context/Setup/ProductAttributeContext.php +++ b/tests/Behat/Context/Setup/ProductAttributeContext.php @@ -23,38 +23,13 @@ final class ProductAttributeContext implements Context { - /** @var SharedStorageInterface */ - private $sharedStorage; - - /** @var RepositoryInterface */ - private $productAttributeRepository; - - /** @var AttributeFactoryInterface */ - private $productAttributeFactory; - - /** @var FactoryInterface */ - private $productAttributeValueFactory; - - /** @var EntityManagerInterface */ - private $objectManager; - - /** @var \Faker\Generator */ - private $faker; - public function __construct( - SharedStorageInterface $sharedStorage, - RepositoryInterface $productAttributeRepository, - AttributeFactoryInterface $productAttributeFactory, - FactoryInterface $productAttributeValueFactory, - EntityManagerInterface $objectManager + private SharedStorageInterface $sharedStorage, + private RepositoryInterface $productAttributeRepository, + private AttributeFactoryInterface $productAttributeFactory, + private FactoryInterface $productAttributeValueFactory, + private EntityManagerInterface $objectManager ) { - $this->sharedStorage = $sharedStorage; - $this->productAttributeRepository = $productAttributeRepository; - $this->productAttributeFactory = $productAttributeFactory; - $this->productAttributeValueFactory = $productAttributeValueFactory; - $this->objectManager = $objectManager; - - $this->faker = \Faker\Factory::create(); } /** diff --git a/tests/Behat/Context/Setup/ProductContext.php b/tests/Behat/Context/Setup/ProductContext.php index 606a351b..511f14a3 100644 --- a/tests/Behat/Context/Setup/ProductContext.php +++ b/tests/Behat/Context/Setup/ProductContext.php @@ -29,57 +29,20 @@ final class ProductContext implements Context { - /** @var SharedStorageInterface */ - private $sharedStorage; - - /** @var ProductRepositoryInterface */ - private $productRepository; - - /** @var ProductFactoryInterface */ - private $productFactory; - - /** @var FactoryInterface */ - private $channelPricingFactory; - - /** @var FactoryInterface */ - private $productOptionFactory; - - /** @var FactoryInterface */ - private $productOptionValueFactory; - - /** @var EntityManagerInterface */ - private $objectManager; - - /** @var ProductVariantResolverInterface */ - private $defaultVariantResolver; - - /** @var SlugGeneratorInterface */ - private $slugGenerator; - /** @var \Faker\Generator */ private $faker; public function __construct( - SharedStorageInterface $sharedStorage, - ProductRepositoryInterface $productRepository, - ProductFactoryInterface $productFactory, - FactoryInterface $channelPricingFactory, - FactoryInterface $productOptionFactory, - FactoryInterface $productOptionValueFactory, - EntityManagerInterface $objectManager, - ProductVariantResolverInterface $defaultVariantResolver, - SlugGeneratorInterface $slugGenerator + private SharedStorageInterface $sharedStorage, + private ProductRepositoryInterface $productRepository, + private ProductFactoryInterface $productFactory, + private FactoryInterface $channelPricingFactory, + private FactoryInterface $productOptionFactory, + private FactoryInterface $productOptionValueFactory, + private EntityManagerInterface $objectManager, + private ProductVariantResolverInterface $defaultVariantResolver, + private SlugGeneratorInterface $slugGenerator ) { - $this->sharedStorage = $sharedStorage; - $this->productRepository = $productRepository; - $this->productFactory = $productFactory; - $this->channelPricingFactory = $channelPricingFactory; - $this->productOptionFactory = $productOptionFactory; - $this->productOptionValueFactory = $productOptionValueFactory; - $this->objectManager = $objectManager; - $this->defaultVariantResolver = $defaultVariantResolver; - $this->slugGenerator = $slugGenerator; - $this->faker = \Faker\Factory::create(); } diff --git a/tests/Behat/Context/Setup/ProductTaxonContext.php b/tests/Behat/Context/Setup/ProductTaxonContext.php index f26c82ec..e07fc0eb 100644 --- a/tests/Behat/Context/Setup/ProductTaxonContext.php +++ b/tests/Behat/Context/Setup/ProductTaxonContext.php @@ -20,23 +20,11 @@ final class ProductTaxonContext implements Context { - /** @var SharedStorageInterface */ - private $sharedStorage; - - /** @var FactoryInterface */ - private $productTaxonFactory; - - /** @var EntityManagerInterface */ - private $objectManager; - public function __construct( - SharedStorageInterface $sharedStorage, - FactoryInterface $productTaxonFactory, - EntityManagerInterface $objectManager + private SharedStorageInterface $sharedStorage, + private FactoryInterface $productTaxonFactory, + private EntityManagerInterface $objectManager ) { - $this->sharedStorage = $sharedStorage; - $this->productTaxonFactory = $productTaxonFactory; - $this->objectManager = $objectManager; } /** diff --git a/tests/Behat/Context/Ui/Shop/HomepageContext.php b/tests/Behat/Context/Ui/Shop/HomepageContext.php index dd0dcea2..414d1267 100644 --- a/tests/Behat/Context/Ui/Shop/HomepageContext.php +++ b/tests/Behat/Context/Ui/Shop/HomepageContext.php @@ -9,12 +9,9 @@ class HomepageContext implements Context { - /** @var HomePageInterface */ - private $homePage; - - public function __construct(HomePageInterface $homePage) - { - $this->homePage = $homePage; + public function __construct( + private HomePageInterface $homePage + ) { } /** diff --git a/tests/Behat/Context/Ui/Shop/ProductContext.php b/tests/Behat/Context/Ui/Shop/ProductContext.php index 6ec0a051..15eb9a85 100644 --- a/tests/Behat/Context/Ui/Shop/ProductContext.php +++ b/tests/Behat/Context/Ui/Shop/ProductContext.php @@ -18,16 +18,10 @@ final class ProductContext implements Context { - /** @var SharedStorageInterface */ - private $sharedStorage; - - /** @var IndexPageInterface */ - private $productIndexPage; - - public function __construct(IndexPageInterface $productIndexPage, SharedStorageInterface $sharedStorage) - { - $this->productIndexPage = $productIndexPage; - $this->sharedStorage = $sharedStorage; + public function __construct( + private IndexPageInterface $productIndexPage, + private SharedStorageInterface $sharedStorage + ) { } /** diff --git a/tests/Behat/Context/Ui/Shop/SearchContext.php b/tests/Behat/Context/Ui/Shop/SearchContext.php index 3997d29f..f6ccefa1 100644 --- a/tests/Behat/Context/Ui/Shop/SearchContext.php +++ b/tests/Behat/Context/Ui/Shop/SearchContext.php @@ -11,12 +11,9 @@ final class SearchContext implements Context { - /** @var SearchPageInterface */ - private $searchPage; - - public function __construct(SearchPageInterface $searchPage) - { - $this->searchPage = $searchPage; + public function __construct( + private SearchPageInterface $searchPage + ) { } /** diff --git a/tests/Behat/Page/Shop/SearchPage.php b/tests/Behat/Page/Shop/SearchPage.php index f9eda7b5..239f7f22 100644 --- a/tests/Behat/Page/Shop/SearchPage.php +++ b/tests/Behat/Page/Shop/SearchPage.php @@ -42,10 +42,10 @@ protected function getDefinedElements(): array 'search_facets_price' => '#bitbag_elasticsearch_search_facets_price', 'search_facets_taxon' => '#bitbag_elasticsearch_search_facets_taxon', 'search_facets_filter_button' => '#filters-vertical form button[type="submit"]', - 'search_facets_attribute_car_type' => '#bitbag_elasticsearch_search_facets_attribute_car_type', - 'search_facets_attribute_motorbike_type' => '#bitbag_elasticsearch_search_facets_attribute_motorbike_type', - 'search_facets_attribute_color' => '#bitbag_elasticsearch_search_facets_attribute_color', - 'search_facets_option_supply' => '#bitbag_elasticsearch_search_facets_option_supply', + 'search_facets_attribute_car_type' => '[data-test-bitbag_elasticsearch_search_facets_car_type]', + 'search_facets_attribute_motorbike_type' => '[data-test-bitbag_elasticsearch_search_facets_motorbike_type]', + 'search_facets_attribute_color' => '[data-test-bitbag_elasticsearch_search_facets_color]', + 'search_facets_option_supply' => '[data-test-bitbag_elasticsearch_search_facets_supply]', ]; } diff --git a/tests/Behat/Service/Populate.php b/tests/Behat/Service/Populate.php index 5ca2ed8a..68a5d1d7 100644 --- a/tests/Behat/Service/Populate.php +++ b/tests/Behat/Service/Populate.php @@ -14,43 +14,19 @@ use FOS\ElasticaBundle\Event\PreIndexPopulateEvent; use FOS\ElasticaBundle\Index\IndexManager; use FOS\ElasticaBundle\Index\ResetterInterface; -use FOS\ElasticaBundle\Persister\PagerPersisterInterface; use FOS\ElasticaBundle\Persister\PagerPersisterRegistry; use FOS\ElasticaBundle\Provider\PagerProviderRegistry; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class Populate { - /** @var EventDispatcherInterface */ - private $dispatcher; - - /** @var IndexManager */ - private $indexManager; - - /** @var PagerProviderRegistry */ - private $pagerProviderRegistry; - - /** @var PagerPersisterRegistry */ - private $pagerPersisterRegistry; - - /** @var PagerPersisterInterface */ - private $pagerPersister; - - /** @var ResetterInterface */ - private $resetter; - public function __construct( - EventDispatcherInterface $dispatcher, - IndexManager $indexManager, - PagerProviderRegistry $pagerProviderRegistry, - PagerPersisterRegistry $pagerPersisterRegistry, - ResetterInterface $resetter + private EventDispatcherInterface $dispatcher, + private IndexManager $indexManager, + private PagerProviderRegistry $pagerProviderRegistry, + private PagerPersisterRegistry $pagerPersisterRegistry, + private ResetterInterface $resetter ) { - $this->dispatcher = $dispatcher; - $this->indexManager = $indexManager; - $this->pagerProviderRegistry = $pagerProviderRegistry; - $this->pagerPersisterRegistry = $pagerPersisterRegistry; - $this->resetter = $resetter; } public function populateIndex(): void