Product + Category feature integration

Edit on GitHub

This document describes how to integrate the Product + Category feature into a Spryker project.

Install feature core

Follow the steps below to install the Product + Category feature core.

Prerequisites

To start feature integration, overview and install the necessary features:

NAME VERSION
Spryker Core 202108.0
Category Management 202108.0
Product 202108.0

1) Install the required modules using Composer

Install the required modules:

composer require spryker-feature/product:"202108.0" --update-with-dependencies
Verification

Make sure the following modules have been installed:

MODULE EXPECTED DIRECTORY
ProductCategory vendor/spryker/product-category
ProductCategorySearch vendor/spryker/product-category-search
ProductCategoryStorage vendor/spryker/product-category-storage
ProductCategoryFilterStorage vendor/spryker/product-category-filter-storage

2) Set up configuration

Set up the following configuration:

src/Pyz/Zed/ProductCategoryStorage/ProductCategoryStorageConfig.php

<?php

namespace Pyz\Zed\ProductCategoryStorage;

use Pyz\Zed\Synchronization\SynchronizationConfig;
use Spryker\Shared\Publisher\PublisherConfig;
use Spryker\Zed\ProductCategoryStorage\ProductCategoryStorageConfig as SprykerProductCategoryStorageConfig;

class ProductCategoryStorageConfig extends SprykerProductCategoryStorageConfig
{
    /**
     * @return string|null
     */
    public function getProductCategorySynchronizationPoolName(): ?string
    {
        return SynchronizationConfig::DEFAULT_SYNCHRONIZATION_POOL_NAME;
    }

    /**
     * @return string|null
     */
    public function getEventQueueName(): ?string
    {
        return PublisherConfig::PUBLISH_QUEUE;
    }
}

src/Pyz/Zed/ProductCategoryFilterStorage/ProductCategoryFilterStorageConfig.php

<?php

namespace Pyz\Zed\ProductCategoryFilterStorage;

use Pyz\Zed\Synchronization\SynchronizationConfig;
use Spryker\Shared\Publisher\PublisherConfig;
use Spryker\Zed\ProductCategoryFilterStorage\ProductCategoryFilterStorageConfig as SprykerProductCategoryFilterStorageConfig;

class ProductCategoryFilterStorageConfig extends SprykerProductCategoryFilterStorageConfig
{
    /**
     * @return string|null
     */
    public function getProductCategoryFilterSynchronizationPoolName(): ?string
    {
        return SynchronizationConfig::DEFAULT_SYNCHRONIZATION_POOL_NAME;
    }

    /**
     * @return string|null
     */
    public function getEventQueueName(): ?string
    {
        return PublisherConfig::PUBLISH_QUEUE;
    }
}

3) Set up database schema and transfer objects 

  1. Set up synchronization queue pools so the entities without store relations are synchronized among stores:

src/Pyz/Zed/ProductCategoryFilterStorage/Persistence/Propel/Schema/spy_product_category_filter_storage.schema.xml

<?xml version="1.0"?>
<database
    xmlns="spryker:schema-01"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    name="zed"
    xsi:schemaLocation="spryker:schema-01 https://static.spryker.com/schema-01.xsd"
    namespace="Orm\Zed\ProductCategoryFilterStorage\Persistence"
    package="src.Orm.Zed.ProductCategoryFilterStorage.Persistence"
>

    <table name="spy_product_category_filter_storage">
        <behavior name="synchronization">
            <parameter name="queue_pool" value="synchronizationPool"/>
        </behavior>
    </table>
</database>
  1. Apply database changes and generate entity and transfer changes:
console transfer:generate
console propel:install
console transfer:generate
Verification

Make sure that the following changes have been applied in the database.

4) Configure export to Redis

Configure tables to be published to the spy_product_abstract_category_storage, spy_product_category_filter_storage and synchronized to the Storage on create, edit, and delete changes:

  1. Set up publisher plugins:
PLUGIN SPECIFICATION PRERQUISITES NAMESPACE
CategoryIsActiveAndCategoryKeyWritePublisherPlugin Publishes product category data by the SpyCategory entity events with modified columns. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category
CategoryStoreDeletePublisherPlugin Publishes product category data by the CategoryStore un-publish event. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category
CategoryStoreWriteForPublishingPublisherPlugin Publishes product category data by the CategoryStore publish event. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category
CategoryStoreWritePublisherPlugin Publishes product category data by theSpyCategoryStore entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category
CategoryWritePublisherPlugin Publishes product category data by the SpyCategory entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category
CategoryAttributeNameWritePublisherPlugin Publishes product category data by theSpyCategoryAttribute entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryAttribute
CategoryAttributeWritePublisherPlugin Publishes product category data by theSpyCategoryAttribute entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryAttribute
CategoryNodeWritePublisherPlugin Publishes product category data by the SpyCategoryNode entity events with modified columns. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryNode
CategoryUrlAndResourceCategorynodeWritePublisherPlugin Publishes product category data by the SpyUrl entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryUrl
CategoryUrlWritePublisherPlugin Publishes product category data by the SpyUrl entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryUrl
ProductCategoryWriteForPublishingPublisherPlugin Publishes product category data by the ProductCategory publishing events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\ProductCategory
ProductCategoryWritePublisherPlugin Publishes product category data by theSpyProductCategory entity events. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\ProductCategory

src/Pyz/Zed/Publisher/PublisherDependencyProvider.php

<?php

namespace Pyz\Zed\Publisher;

use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category\CategoryIsActiveAndCategoryKeyWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category\CategoryStoreDeletePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category\CategoryStoreWriteForPublishingPublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category\CategoryStoreWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\Category\CategoryWritePublisherPlugin as ProductCategoryStorageCategoryWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryAttribute\CategoryAttributeNameWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryAttribute\CategoryAttributeWritePublisherPlugin as ProductCategoryAttributeWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryNode\CategoryNodeWritePublisherPlugin as ProductCategoryNodeWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryUrl\CategoryUrlAndResourceCategorynodeWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\CategoryUrl\CategoryUrlWritePublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\ProductCategory\ProductCategoryWriteForPublishingPublisherPlugin;
use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\ProductCategory\ProductCategoryWritePublisherPlugin;
use Spryker\Zed\Publisher\PublisherDependencyProvider as SprykerPublisherDependencyProvider;

class PublisherDependencyProvider extends SprykerPublisherDependencyProvider
{
    /**
     * @return array
     */
    protected function getPublisherPlugins(): array
    {
        return array_merge(
            $this->getProductCategoryStoragePlugins(),
        );
    }

    /**
     * @return \Spryker\Zed\PublisherExtension\Dependency\Plugin\PublisherPluginInterface[]
     */
    protected function getProductCategoryStoragePlugins(): array
    {
        return [
            new CategoryStoreWritePublisherPlugin(),
            new CategoryStoreWriteForPublishingPublisherPlugin(),
            new CategoryStoreDeletePublisherPlugin(),
            new ProductCategoryStorageCategoryWritePublisherPlugin(),
            new CategoryIsActiveAndCategoryKeyWritePublisherPlugin(),
            new ProductCategoryAttributeWritePublisherPlugin(),
            new CategoryAttributeNameWritePublisherPlugin(),
            new ProductCategoryNodeWritePublisherPlugin(),
            new CategoryUrlWritePublisherPlugin(),
            new CategoryUrlAndResourceCategorynodeWritePublisherPlugin(),
            new ProductCategoryWriteForPublishingPublisherPlugin(),
            new ProductCategoryWritePublisherPlugin(),
        ];
    }
}
  1. Set up event listeners:
PLUGIN SPECIFICATION PRERQUISITES NAMESPACE
ProductCategoryFilterStorageEventSubscriber Registers listeners that publish category information to the storage when a related entity changes. Spryker\Zed\ProductCategoryFilterStorage\Communication\Plugin\Event\Subscriber

src/Pyz/Zed/Event/EventDependencyProvider.php

<?php

namespace Pyz\Zed\Event;

use Spryker\Zed\Event\EventDependencyProvider as SprykerEventDependencyProvider;
use Spryker\Zed\ProductCategoryFilterStorage\Communication\Plugin\Event\Subscriber\ProductCategoryFilterStorageEventSubscriber;

class EventDependencyProvider extends SprykerEventDependencyProvider
{
    /**
     * @return \Spryker\Zed\Event\Dependency\EventSubscriberCollectionInterface
     */
    public function getEventSubscriberCollection()
    {
        $eventSubscriberCollection = parent::getEventSubscriberCollection();
        $eventSubscriberCollection->add(new ProductCategoryFilterStorageEventSubscriber());

        return $eventSubscriberCollection;
    }
}
  1. Set up trigger plugins:
PLUGIN SPECIFICATION PRERQUISITES NAMESPACE
ProductCategoryPublisherTriggerPlugin Retrieves product categories based on the provided limit and offset. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher

src/Pyz/Zed/Publisher/PublisherDependencyProvider.php

<?php

namespace Pyz\Zed\Publisher;

use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Publisher\ProductCategoryPublisherTriggerPlugin;
use Spryker\Zed\Publisher\PublisherDependencyProvider as SprykerPublisherDependencyProvider;

class PublisherDependencyProvider extends SprykerPublisherDependencyProvider
{
    /**
     * @return \Spryker\Zed\PublisherExtension\Dependency\Plugin\PublisherTriggerPluginInterface[]
     */
    protected function getPublisherTriggerPlugins(): array
    {
        return [
            new ProductCategoryPublisherTriggerPlugin(),
        ];
    }
}
  1. Set up synchronization plugins:
PLUGIN SPECIFICATION PRERQUISITES NAMESPACE
ProductCategorySynchronizationDataBulkRepositoryPlugin Retrieves a product abstract category storage collection according to the provided offset, limit, and IDs. Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Synchronization
ProductCategoryFilterSynchronizationDataPlugin Retrieves a product category filter storage collection according to the provided offset, limit, and IDs. Spryker\Zed\ProductCategoryFilterStorage\Communication\Plugin\Synchronization

src/Pyz/Zed/Synchronization/SynchronizationDependencyProvider.php

<?php

namespace Pyz\Zed\Synchronization;

use Spryker\Zed\ProductCategoryStorage\Communication\Plugin\Synchronization\ProductCategorySynchronizationDataBulkRepositoryPlugin;
use Spryker\Zed\ProductCategoryFilterStorage\Communication\Plugin\Synchronization\ProductCategoryFilterSynchronizationDataPlugin;
use Spryker\Zed\Synchronization\SynchronizationDependencyProvider as SprykerSynchronizationDependencyProvider;

class SynchronizationDependencyProvider extends SprykerSynchronizationDependencyProvider
{
    /**
     * @return \Spryker\Zed\SynchronizationExtension\Dependency\Plugin\SynchronizationDataPluginInterface[]
     */
    protected function getSynchronizationDataPlugins(): array
    {
        return [
            new ProductCategorySynchronizationDataBulkRepositoryPlugin(),
            new ProductCategoryFilterSynchronizationDataPlugin(),
        ];
    }
}
  1. Set up re-generate and re-sync Features
PLUGIN SPECIFICATION PRERQUISITES NAMESPACE
ProductCategoryFilterEventResourceQueryContainerPlugin Allows populating an empty search table with data. Spryker\Zed\ProductCategoryFilterStorage\Communication\Plugin\Event

src/Pyz/Zed/EventBehavior/EventBehaviorDependencyProvider.php

<?php

namespace Pyz\Zed\EventBehavior;

use Spryker\Zed\EventBehavior\EventBehaviorDependencyProvider as SprykerEventBehaviorDependencyProvider;
use Spryker\Zed\ProductCategoryFilterStorage\Communication\Plugin\Event\ProductCategoryFilterEventResourceQueryContainerPlugin;

class EventBehaviorDependencyProvider extends SprykerEventBehaviorDependencyProvider
{
    /**
     * @return \Spryker\Zed\EventBehavior\Dependency\Plugin\EventResourcePluginInterface[]
     */
    protected function getEventTriggerResourcePlugins()
    {
        return [
            new ProductCategoryFilterEventResourceQueryContainerPlugin(),
        ];
    }
}
Verification

Make sure that, when a category product assignment is changed through ORM, it is exported to Redis.

STORAGE TYPE TARGET ENTITY EXAMPLE EXPECTED DATA IDENTIFIER
Redis ProductAbstractCategory product_abstract_category:de:de_de:1
Redis ProductCategoryFilter product_category_filter:8

EXAMPLE EXPECTED DATA GRAGMENT: product_abstract_category:de:de_de:1

{
    "id_product_abstract": 1,
    "categories": [
        {
            "category_id": 4,
            "category_node_id": 4,
            "name": "Digitale Kameras",
            "url": "/de/kameras-&-camcorders/digitale-kameras"
        },
        {
            "category_id": 2,
            "category_node_id": 2,
            "name": "Kameras & Camcorders",
            "url": "/de/kameras-&-camcorders"
        }
    ],
    "_timestamp": 1622146779.587637
}

EXAMPLE EXPECTED DATA GRAGMENT: product_category_filter:8

{
   "id_category":8,
   "filter_data":{
      "filters":[
         {
            "isActive":true,
            "label":"label",
            "key":"label"
         },
         {
            "isActive":false,
            "label":"color",
            "key":"color"
         },
         {
            "isActive":false,
            "label":"storage_capacity",
            "key":"storage_capacity"
         },
         {
            "isActive":false,
            "label":"brand",
            "key":"brand"
         },
         {
            "isActive":false,
            "label":"touchscreen",
            "key":"touchscreen"
         },
         {
            "isActive":false,
            "label":"weight",
            "key":"weight"
         },
         {
            "isActive":false,
            "label":"merchant_name",
            "key":"merchant_name"
         },
         {
            "isActive":false,
            "label":"category",
            "key":"category"
         },
         {
            "isActive":false,
            "label":"price",
            "key":"price-DEFAULT-EUR-GROSS_MODE"
         },
         {
            "isActive":false,
            "label":"rating",
            "key":"rating"
         }
      ]
   }
}

5) Set up behavior

Add the following plugins to your project:

PLUGIN SPECIFICATION PRERQUISITES NAMESPACE
ProductCategoryMapExpanderPlugin Expands PageMapTransfer with category map data. Spryker\Zed\ProductCategorySearch\Communication\Plugin\ProductPageSearch\Elasticsearch
ProductCategoryPageDataExpanderPlugin Expands ProductPageSearchTransfer with category related data. Spryker\Zed\ProductCategorySearch\Communication\Plugin\ProductPageSearch
ProductCategoryPageDataLoaderPlugin Expands ProductPayloadTransfer.categories with product category entities. Spryker\Zed\ProductCategorySearch\Communication\Plugin\ProductPageSearch
ProductCategoryRelationReadPlugin Gets localized products abstract names by category. Spryker\Zed\ProductCategory\Communication\Plugin\CategoryGui
RemoveProductCategoryRelationPlugin Removes relations between category and products. Spryker\Zed\ProductCategory\Communication\Plugin
UpdateProductCategoryRelationPlugin Updates relations between category and products. Spryker\Zed\ProductCategory\Communication\Plugin

src/Pyz/Zed/ProductPageSearch/ProductPageSearchDependencyProvider.php

<?php

namespace Pyz\Zed\ProductPageSearch;

use Spryker\Zed\ProductCategorySearch\Communication\Plugin\ProductPageSearch\Elasticsearch\ProductCategoryMapExpanderPlugin;
use Spryker\Zed\ProductCategorySearch\Communication\Plugin\ProductPageSearch\ProductCategoryPageDataExpanderPlugin;
use Spryker\Zed\ProductCategorySearch\Communication\Plugin\ProductPageSearch\ProductCategoryPageDataLoaderPlugin;
use Spryker\Zed\ProductPageSearch\ProductPageSearchDependencyProvider as SprykerProductPageSearchDependencyProvider;

class ProductPageSearchDependencyProvider extends SprykerProductPageSearchDependencyProvider
{
    /**
     * @return \Spryker\Zed\ProductPageSearchExtension\Dependency\Plugin\ProductAbstractMapExpanderPluginInterface[]
     */
    protected function getProductAbstractMapExpanderPlugins(): array
    {
        return [
            new ProductCategoryMapExpanderPlugin(),
        ];
    }

    /**
     * @return \Spryker\Zed\ProductPageSearch\Dependency\Plugin\ProductPageDataExpanderInterface[]
     */
    protected function getDataExpanderPlugins()
    {
        $dataExpanderPlugins = [];
        $dataExpanderPlugins[ProductPageSearchConfig::PLUGIN_PRODUCT_CATEGORY_PAGE_DATA] = new ProductCategoryPageDataExpanderPlugin();

        return $dataExpanderPlugins;
    }

    /**
     * @return \Spryker\Zed\ProductPageSearchExtension\Dependency\Plugin\ProductPageDataLoaderPluginInterface[]
     */
    protected function getDataLoaderPlugins()
    {
        return [
            new ProductCategoryPageDataLoaderPlugin(),
        ];
    }
}
Verification

Make sure that the product abstract contains the category property in Elasticsearch:

Data fragment

{
   "_id":"product_abstract:at:de_de:42",
   "_source":{
      "store":"AT",
      "locale":"de_DE",
      "type":"product_abstract",
      "is-active":true,
      "search-result-data":{
         "id_product_abstract":42,
         "abstract_sku":"042",
         "abstract_name":"Samsung Galaxy S7",
         "url":"/de/samsung-galaxy-s7-42",
         "type":"product_abstract",
         "add_to_cart_sku":"042_31040075",
      },
      "category":{
         "all-parents":[
            11,
            12,
            1
         ],
         "direct-parents":[
            "12"
         ]
      },
   }
}

src/Pyz/Zed/CategoryGui/CategoryGuiDependencyProvider.php

<?php

namespace Pyz\Zed\CategoryGui;

use Spryker\Zed\CategoryGui\CategoryGuiDependencyProvider as SpykerCategoryGuiDependencyProvider;
use Spryker\Zed\ProductCategory\Communication\Plugin\CategoryGui\ProductCategoryRelationReadPlugin;

/**
 * @method \Spryker\Zed\CategoryGui\CategoryGuiConfig getConfig()
 */
class CategoryGuiDependencyProvider extends SpykerCategoryGuiDependencyProvider
{
    /**
     * @return \Spryker\Zed\CategoryGuiExtension\Dependency\Plugin\CategoryRelationReadPluginInterface[]
     */
    protected function getCategoryRelationReadPlugins(): array
    {
        return [
            new ProductCategoryRelationReadPlugin(),
        ];
    }
}

Verification

Make sure that, on the Delete Category in the Back Office, the Products to be de-assigned column is displayed.

src/Pyz/Zed/Category/CategoryDependencyProvider.php

<?php

namespace Pyz\Zed\Category;

use Spryker\Zed\Category\CategoryDependencyProvider as SprykerDependencyProvider;
use Spryker\Zed\ProductCategory\Communication\Plugin\RemoveProductCategoryRelationPlugin;
use Spryker\Zed\ProductCategory\Communication\Plugin\UpdateProductCategoryRelationPlugin;

class CategoryDependencyProvider extends SprykerDependencyProvider
{
    /**
     * @return \Spryker\Zed\CategoryExtension\Dependency\Plugin\CategoryRelationDeletePluginInterface[]
     */
    protected function getRelationDeletePluginStack(): array
    {
        $deletePlugins = array_merge(
            [
                new RemoveProductCategoryRelationPlugin(),
            ],
            parent::getRelationDeletePluginStack()
        );

        return $deletePlugins;
    }

    /**
     * @return \Spryker\Zed\CategoryExtension\Dependency\Plugin\CategoryRelationUpdatePluginInterface[]
     */
    protected function getRelationUpdatePluginStack(): array
    {
        return array_merge(
            [
                new UpdateProductCategoryRelationPlugin(),
            ],
            parent::getRelationUpdatePluginStack()
        );
    }
}
Verification

Make sure that, after updating or removing a category in the Back Office, the product assignments are changed respectively.

Install feature front end

To install the Category Management feature front end, follow the steps below.

Prerequisites

Overview and install the following features.

NAME VERSION
Spryker Core 202108.0
Category master

1) Install the required modules using Composer

Install the required modules:

composer require spryker-feature/product:"202108.0" --update-with-dependencies
Verification

Make sure that the following modules have been installed:

MODULE EXPECTED DIRECTORY
ProductCategoryWidget vendor/spryker-shop/product-category-widget

2) Set up widgets

Register the following global widgets:

WIDGET DESCRIPTION NAMESPACE
ProductBreadcrumbsWithCategoriesWidget Displays category breadcrumbs on the Product Details page. SprykerShop\Yves\ProductCategoryWidget\Widget

src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php

<?php   

namespace Pyz\Yves\ShopApplication;   

use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;
use SprykerShop\Yves\ProductBundleWidget\Widget\ProductBundleItemsMultiCartItemsListWidget;

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{     
	/**      
	* @return string[]      
	*/     
	protected function getGlobalWidgets(): array     
	{         
		return [             
			ProductBreadcrumbsWithCategoriesWidget::class,         
		];     
	}
}
Verification

Make sure that category breadcrumbs are displayed on the Product Details page.

Integrate the following related features:

FEATURE REQUIRED FOR THE CURRENT FEATURE INTEGRATION GUIDE
Category Management Feature Category Management feature integration
Product Management Feature Product feature integration
Glue API: Category Management Glue API: Category management feature integration
Catalog + Category Management Catalog + Category Management feature integration
CMS + Category Management CMS + Category Management feature integration