Мой клиент желает, чтобы был создан каталог продуктов с различными атрибутами в зависимости от категории. Клиент также желает иметь достаточно всеобъемлющую возможность поиска, которая проверяет названия продуктов и их атрибуты. Требуемый формат отображения сгруппирован по категориям, а затем в таблице продуктов, которые соответствуют поисковому запросу.
Category
- name (string)
- products (1-to-Many:Product)
- attributes (1-to-Many:Attribute)
Attribute
- name (string)
- isDropdown (bool) - flag meaning can either be custom or from the dropdown options
- attributeOptions (1-to-Many:AttributeOption)
AttributeOption
- attribute (Many-to-1:Attribute)
- value
ProductAttributeValue
- attribute (Many-to-1:Attribute)
- selectedOption (Many-to-1:AttributeOption)
- value (string)
Product
- name (string)
- attributeValues (1-to-Many:ProductAttributeValue)
- category (Many-to-1:Category)
Поиск по названию продукта достаточно прост:
$search = 'gloves and stuff';
$searchTerms = explode(' ', $search);
$categoriesWithProducts = $em->createQueryBuilder()
->select('c, p, a, av, ao')
->from('AcmeBundle:Category', 'c')
->innerJoin('c.products', 'p')
->leftJoin('c.attributes', 'a')
->leftJoin('p.attributeValues', 'av')
->leftJoin('av.selectedOption', 'ao')
;
$i = 0;
foreach ($searchTerm as $st) {
$categoriesWithProducts
->orWhere('p.name LIKE ?'.$i)
->setParameter($i++, '%' . $st . '%')
;
}
$categoriesWithProducts = $categoriesWithProducts->getQuery()->getResult();
И вывод достигается с помощью этого кода Twig:
{% for category in categoriesWithProducts %}
<h2>{{ category.name }}</h2>
<table>
<thead>
{% for attribute in category.attributes %}
<th>{{ attribute.name }}</th>
{% endfor %}
</thead>
<tbody>
{% for product in category.products %}
<tr>
<td>{{ product.name }}</td>
{% for product in category.attributes %}
<td>
{% for attributeValue in product.attributeValues if attributeValue.attribute == attribute %}
{{ attribute.isDropdown ? attributeValue.selectedOption.value : attributeValue.value }}
{% endfor %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
Результирующий вывод:
Однако, когда я изменяю запрос, чтобы он также соответствовал атрибутам из условий поиска:
$categoriesWithProducts = $em->createQueryBuilder()
->select('c, p, a, av, ao')
->from('AcmeBundle:Category', 'c')
->innerJoin('c.products', 'p')
->leftJoin('c.attributes', 'a')
->leftJoin('p.attributeValues', 'av')
->leftJoin('av.selectedOption', 'ao')
;
$i = 0;
foreach ($searchTerm as $st) {
$categoriesWithProducts
->orWhere('p.name LIKE ?'.$i)
->orWhere('av.value LIKE ?'.$i)
->orWhere('ao.value LIKE ?'.$i)
->setParameter($i++, '%' . $st . '%')
;
}
Поиск по-прежнему будет работать для совпадающих названий продуктов, но производить подмножества attributeValues
а также selectedOption
когда совпадения найдены из $search
срок. Например, с $search = 'red'
:
А также $search = 'red small'
:
Я попытался использовать два объединения для обеих связанных сущностей, но в итоге получал дубликаты в моем наборе результатов, что приводит к двойному выводу в моем рендере Twig.
Код:
$categoriesWithProducts = $em->createQueryBuilder()
->select('c, p, a, av, ao, avSearch, aoSearch')
->from('AcmeBundle:Category', 'c')
->innerJoin('c.products', 'p')
->leftJoin('c.attributes', 'a')
->leftJoin('p.attributeValues', 'av')
->leftJoin('av.selectedOption', 'ao')
->leftJoin('p.attributeValues', 'avSearch')
->leftJoin('av.selectedOption', 'aoSearch')
;
$i = 0;
foreach ($searchTerm as $st) {
$categoriesWithProducts
->orWhere('p.name LIKE ?'.$i)
->orWhere('avSearch.value LIKE ?'.$i)
->orWhere('aoSearch.value LIKE ?'.$i)
->setParameter($i++, '%' . $st . '%')
;
}
Результат с $search = 'gloves and stuff'
:
И с $search = 'red small'
(обратите внимание на дубликат Red
а также Small
записи, но единственные результаты для других атрибутов):
Я пробовал разные способы скрыть aoSearch
а также avSearch
Результаты:
HIDDEN aoSearch
а также HIDDEN avSearch
ничего не делаетaoSearch
а также avSearch
от SELECT
возвращается к проблеме неисправного подмножества, найденной на 2-м и 3-м скриншотах.Я хочу получить все атрибуты для продукта, показанного в таблице, если найдено совпадение среди любых атрибутов продукта. Есть ли способ, которым я могу скрыть спички от aoSearch
а также avSearch
что я пропустил?
Только через полчаса после публикации этого вопроса, и у меня есть обходной путь:
Единственный способ найти решение до сих пор — это использовать EXISTS
функция в пределах одного orWhere
предложение, поэтому совпадение содержится в подзапросе:
$categoriesWithProducts = $em->createQueryBuilder()
->select('c, p, a, av, ao')
->from('AcmeBundle:Category', 'c')
->innerJoin('c.products', 'p')
->leftJoin('c.attributes', 'a')
->leftJoin('p.attributeValues', 'av')
->leftJoin('av.selectedOption', 'ao')
;
$i = 0;
$subQuery = '';
foreach ($searchTerm as $st) {
if ($i > 0) $subQuery .= ' OR ';
$subQuery = 'sao.text LIKE ?'.$i.' OR pav.text LIKE ?'.$i;
$categoriesWithProducts
->orWhere('p.name LIKE ?'.$i)
->setParameter($i++, '%' . $st . '%')
;
}
$categoriesWithProducts->orWhere(
'EXISTS (
SELECT pav
FROM AcmeBundle:ProductAttributeValue pav
LEFT JOIN pav.selectedOption sao
WHERE pav.product = p AND ('.$subQuery.'))
');
$categoriesWithProducts = $categoriesWithProducts->getQuery()->getResult();
Это дает мне отличные результаты поиска с изобилием атрибутов!
Других решений пока нет …