Benjamin Cremer
        
Core Developer shopware AG
    
    @benjamincremer
    
        BaseProduct < ListProduct < Product
    
    BaseProduct: Product Identification
ListProduct: Basic Product Data (Catogory Listing, Related Products)
Product: Full Product data, contains ListProducts (Product Detail Page)
$criteria = new \Shopware\Bundle\SearchBundle\Criteria();
    
    
$criteria->addCondition(
    new HasFreeShippingCondition()
);
    
    
$criteria->addCondition(
    new SearchTermCondition('tee')
);
    
    
$criteria->addCondition(
    new PriceRangeCondition(5, 50)
);
    
    
$criteria->addSorting(
    new PriceSorting(SortingInterface::SORT_ASC)
);
    
    
$criteria->limit(5);
$criteria->offset(0);
    
    
namespace Shopware\Bundle\SearchBundle\Condition;
class PriceRangeCondition implements ConditionInterface
{
    /**
     * @param float $minPrice
     * @param float $maxPrice
     */
    public function __construct($minPrice, $maxPrice)
    {
        Assertion::float($minPrice); // see github.com/beberlei/assert
        Assertion::float($maxPrice);
        $this->minPrice = $minPrice;
        $this->maxPrice = $maxPrice;
    }
    public function getName() {};
    public function getMinPrice() {};
    public function getMaxPrice() {};
}
    
/** @var ContextServiceInterface $contextService */
$contextService = $container->get('shopware_storefront.context_service_core');
$context = $contextService->getProductContext();
    
    
$context->getShop()->getId(); // 1
$context->getFallbackCustomerGroup()->getKey(); // EK
    
namespace Shopware\Bundle\SearchBundle;
interface ProductNumberSearchInterface
{
    /**
     * @param Criteria $criteria
     * @param ShopContextInterface $context
     * @return ProductNumberSearchResult
     */
    public function search(Criteria $criteria, ShopContextInterface $context);
}
    
$criteria = Criteria();
$criteria->addCondition(new SearchTermCondition('tee'));
$criteria->limit(5);
[..]
$context = [..]
    
    
/** @var ProductNumberSearchInterface $search */
$search = $this->get('shopware_searchdbal.product_number_search');
/** @var ProductNumberSearchResult $result */
$result = $search->search($criteria, $context);
    
    
echo $result->getTotalCount();
/** @var BaseProduct $product */
foreach ($result->getProducts() as $product) {
    echo $product->getNumber(); // $product->getId(), $product->getVariantId();
}
    
interface ListProductServiceInterface
{
    /**
     * @return Struct\ListProduct
     */
    public function get($number, ProductContextInterface $context);
    /**
     * @return Struct\ListProduct[] Indexed by the products order number
     */
    public function getList(array $numbers, ProductContextInterface $context);
}
    
    
interface ProductServiceInterface
{
    /**
     * @return Struct\Product
     */
    public function get($number, ProductContextInterface $context);
    /**
     * @return Struct\Product[] Indexed by the products order number
     */
    public function getList($numbers, ProductContextInterface $context);
}
    
/** @var ListProductService $productListService */
$productListService = $this->get('shopware_storefront.list_product_service');
/** @var ListProduct $product */
$product = $productListService->get('sw-555', $context);
/** @var ListProduct[] $products */
$products = $productListService->getList(['sw-555', 'sw-111'], $context);
    
/** @var ProductNumberSearchResult $searchResult */
$searchResult = $this->productNumberSearch->search($condition, $context);
/** @var ListProductService $productListService */
$productListService = $this->get('shopware_storefront.list_product_service');
    
    
$listProducts = [];
foreach ($result->getProducts() as $product) {
    $listProducts = $productListService->get($product->getNumber(), $context);
}
    
    
// Better:
$listProducts = $productListService->get(
    array_keys($result->getProducts()),
    $context
);
// Produces one single IN query (default DBAL implementation)
    
namespace Shopware\Bundle\SearchBundle;
use Shopware\Bundle\StoreFrontBundle\Struct;
interface ProductSearchInterface
{
    /**
     * @return ProductSearchResult
     */
    public function search(Criteria $criteria, ProductContextInterface $context);
}
    
class ProductSearch implements ProductSearchInterface
{
    public function __construct(
        ProductNumberSearchInterface $search         // ElasticSearch
        ListProductServiceInterface $productService, // Doctrine DBAL
    );
    /**
     * @return ProductSearchResult
     */
    public function search(Criteria $criteria, ProductContextInterface $context);
}
    
$condition = [..];
$context   = [..];
/** @var ProductSearchInterface $search */
$search = $this->get('shopware_search.product_search');
$result = $search->search($criteria, $context);
echo "Totalcount: ". $result->getTotalCount(). "\n";
/** @var ListProduct $product */
foreach ($result->getProducts() as $product) {
    echo $product->getName();
};
    
namespace ShopwarePlugins\AcmeExamplePlugin;
class CriteriaRequestHandler implements CriteriaRequestHandlerInterface
{
    public function handleRequest(
        Request $request,
        Criteria $criteria,
        ShopContextInterface $context
    ) {
        if (!$request->hasParam('foos')) {
            return;
        }
        $fooIds = explode(
            '|',
            $request->getParam('foos')
        );
        if (!empty($fooIds)) {
            $criteria->addCondition(new FooCondition($fooIds));
        }
    }
}
    
interface ConditionHandlerInterface
{
    /**
     * @param ConditionInterface $condition
     * @return bool
     */
    public function supportsCondition(ConditionInterface $condition);
    /**
     * @param ConditionInterface $condition
     * @param QueryBuilder $query
     * @param ShopContextInterface $context
     */
    public function generateCondition(
        ConditionInterface $condition,
        QueryBuilder $query,
        ShopContextInterface $context
    );
}
    
class ManufacturerConditionHandler implements ConditionHandlerInterface
{
    public function supportsCondition(ConditionInterface $condition)
    {
        return ($condition instanceof ManufacturerCondition);
    }
    public function generateCondition(
        ConditionInterface $condition,
        QueryBuilder $query,
        ShopContextInterface $context
    ) {
        /* @var $condition ManufacturerCondition */
        $query->innerJoin(
            'product',
            's_articles_manufacturer',
            'manufacturer',
            'manufacturer.id = product.manufacturerID
             AND product.manufacturerID IN (:manufacturer)'
        );
        $query->setParameter(
            ':manufacturer',
            $condition->getManufacturerIds(),
            Connection::PARAM_INT_ARRAY
        );
    }
}
    
    
class ProductNameSortingHandler implements SortingHandlerInterface
{
    public function supportsSorting(SortingInterface $sorting)
    {
        return ($sorting instanceof ProductNameSorting);
    }
    public function generateSorting(
        SortingInterface $sorting,
        QueryBuilder $query,
        ShopContextInterface $context
    ) {
        $query->addOrderBy('product.name', $sorting->getDirection())
              ->addOrderBy('product.id', $sorting->getDirection());
    }
}
    
Just add own Conditions & Handlers
class CachedSearch implements SearchInterface
{
    private $innerSearch;
    private $cache;
    public function __construct(SearchInterface $innerSearch, Cache $cache)
    {
        $this->innerSearch = $innerSearch;
        $this->cache = $cache;
    }
    public function search(Criteria $criteria, Struct\ShopContextInterface $context)
    {
        $key = stringify($criteria, $context);
        if ($result = $this->cache->get($key)) {
            return $result;
        }
        $result = $this->innerSearch->search($criteria, $context);
        $this->cache->put($key, $result);
        return $result;
    }
}