Category Filtering
Perch has centralised categories and offers an easy way to filter items by the categories they belong to. There are some limitations you may run into. Let’s review the basics, then dive into the limitations and possible workarounds.
The basics
The official documentation on this are good: Category Filtering. Nonetheless, here is a quick 101:
After categorising some content items, you can filter categories using the category
option (also works with most first-party apps) by passing some category paths in an array:
perch_collection('My Collection', [
'category' => ['set-slug/cat-slug/', 'set-slug/elephant/', 'another-set/unicorn/']
]);
You can also pass a single category path as a string, which is convenient if you know you’re only filtering by one category:
perch_collection('My Collection', [
'category' => 'another-set/unicorn/'
]);
By default, Perch will fetch items that belong to any
of the supplied categories. You can tell Perch to fetch items that belong to all
of the supplied categories instead:
perch_collection('My Collection', [
'category' => ['set-slug/cat-slug/', 'set-slug/elephant/', 'another-set/unicorn/'],
'category-match' => 'all'
]);
You can also exclude categories by prepending !
to a category path:
perch_collection('My Collection', [
'category' => ['set-slug/cat-slug/', '!set-slug/elephant/', 'another-set/unicorn/'],
]);
Let’s recap. You can:
- filter by one or more categories
- filter by categories from multiple category sets
- exclude items that belong to some categories
- specify that you want items that belong to all/any the supplied categories
Limitations
So while the category filtering options give you a decent amount of flexibility to cover a lot of cases, it falls short in some ways.
At the moment (v3), it is not possible to filter by two groups of categories and find items that at least belong to one category from each group. Describing it like this may a rare scenario, but it is actually a common use-case for user-filtered lists.
Let’s say you have a form that let the user filter some products that are categorised by size and colour.
Sizes:
- Small
- Medium
- Large
- X-Large
Colours:
- Red
- Blue
- Yellow
If the user selects the sizes L
and XL
and the colour Red
, the user would typically expect to see products that are either of size Lage or X-Large and that are Red. That is, they’d expect these combinations:
- Large and Red
- X-Large and Red
They would not be interested in Red products that are of size Small, or Blue products that are in size Large. That is, they are not interested in combinations such as:
- Small and Red
- Large and Blue
- X-Large and Yellow
So how do we perform this type filtering? What would happen if we pass the selected categories?
perch_collection('Products', [
'category' => ['sizes/large/', 'sizes/x-large/', 'colours/red/'],
]);
The above would return any items that belong to any of the supplied categories. This would include Blue/Large products and Red/Small products. So that’s not we want here.
Ok what if we apply the all
match option?
perch_collection('Products', [
'category' => ['sizes/large/', 'sizes/x-large/', 'colours/red/'],
'category-match' => 'all',
]);
The above would return items that belong to all the supplied categories. So each item must belong to the Large, X-Large and Red categories. This means we would not get items that only belong to Red/Large categories (but not X-Large) or Red/X-Large categories (but not Large). So this is not what we want either. Hopefully you can see how filtering by more categories with this approach would quickly return no items.
Solution 1: exclude
One way to solve this is to make use of the exclude option:
perch_collection('Products', [
'category' => [
'sizes/large/', 'sizes/x-large/', 'colours/red/',
'!sizes/small/', '!sizes/medium/', '!colours/blue/', '!colours/yellow/',
],
]);
This is a little verbose, but it does what we want to some extent.
Firstly, performing this dynamically requires an additional step too. You’d need to fetch all the categories that were not selected by the user in order to exclude them.
The real issue, however, is the fact that this does not work for all use cases. If a product was categories by the Large, Red and Blue categories, the above query will exclude them (because we are excluding all items that belong to the blue categories). So really this approach only works if you are only categorising items by one category from each set:
<perch:categories id="size" set="sizes" label="Size" max="1" />
<perch:categories id="colour" set="colours" label="Colour" max="1" />
Solution 2: filter
Perch is designed to let you handle category filtering with the category
option, but you can also use the filter
option too:
perch_collection('Products', [
'filter' => '_category',
'match' => 'eq',
'value' => 'sizes/x-large/',
]);
And you can filter by multiple categories like so (this would be the equivalent to using 'category-match' => 'any'
:
perch_collection('Products', [
'filter' => '_category',
'match' => 'in',
'value' => implode(',', ['sizes/large/', 'sizes/x-large/', 'colours/red/']),
]);
The cool thing is you can use the category
and filter
options at the same time:
perch_collection('Products', [
'category' => 'sizes/x-large/',
'filter' => '_category',
'match' => 'eq',
'value' => 'colours/red/',
]);
When you use both options at the same time, Perch will first filter by the category supplied in the category
, then will filter by the categories supplied in the standard filter
option.
This means we can supply the sizes to the category
option, and the colours to the filter
option to achieve what we want:
perch_collection('Products', [
'category' => ['sizes/large/', 'sizes/x-large/'],
'filter' => '_category',
'match' => 'in',
'value' => implode(',', ['colours/blue/', 'colours/red/']),
]);
This works well when filtering by two category sets. However, if you need to filter by more category sets, you can’t really produce the same behaviour reliably.