Search by

pimbay / search-query-pimcore

jansarmir

Pimcore Listing adapter for pimbay/search-query.

v1.0.0 2026-09-29 04:08 UTC

This package is auto-updated.

Last update: 2026-09-29 04:09:20 UTC


README

Latest Version on Packagist PHP Version License Code Coverage Mutation Score

A Pimcore Listing adapter for pimbay/search-query. Adapter\PimcoreListingAdapter implements PageAdapter/SliceAdapter/CountableAdapter/HeadableAdapter/AllAdapter/IdentifiableAdapter — all at once — over a Pimcore listing. SearchTerms\SearchTermsQuery turns an already-parsed SearchTerms\ParsedSearchTerms (from pimbay/search-query's SearchTermsParser) into addConditionParam() calls — free-text search wired into the same listing.

Supports Pimcore ^11.5 || ^12.0 || ^2026.0. 11.5 rather than 11.0 is the floor because 11.0 and 11.1 cap at PHP 8.2, which this package does not support, and 11.5 is the line still in use. Those majors are not licensed alike — 11.x is GPLv3/PCL, 12.x and newer are the Pimcore Open Core License with a revenue threshold. See docs/THIRD-PARTY-NOTICES.md.

Installation

composer require pimbay/search-query-pimcore

Usage

Adapter\PimcoreListingAdapter

A single adapter covers every capability — unlike Doctrine, Pimcore listings have no fetch-join row-multiplication problem, so there's no split into separate adapter classes.

It accepts any of Pimcore's six concrete listing classes: DataObject\Listing, Asset\Listing, Document\Listing, Element\Note\Listing, Element\Tag\Listing and Version\Listing, or a subclass of one of them such as a generated DataObject\Product\Listing. It deliberately does not accept a bare Pimcore\Model\Listing\AbstractListing: the methods this adapter needs are not declared there — see docs/DECISIONS.md.

<?php

declare(strict_types=1);

use PimBay\SearchQuery\Page\PageAssembler;
use PimBay\SearchQuery\Pimcore\Adapter\PimcoreListingAdapter;

$listing = new DataObject\Product\Listing();
$listing->setOrderKey('id');
$listing->setOrder('DESC');

$adapter = new PimcoreListingAdapter($listing);

$result = (new PageAssembler())->paginate($adapter, 1, 20);

ids() needs no field name to configure — the listing's own loadIdList() already knows how to resolve the identifier:

$adapter->ids(); // int[]

count(), ids() and all() are whole-set reads: each resets the listing's offset and limit on its own clone first, so a window left over from an earlier paginated read cannot silently bound them. head() does the same and applies its own limit. Your listing is never mutated — every call works on a clone.

SearchTerms\SearchTermsQuery

Turns a SearchTerms\ParsedSearchTerms into addConditionParam() calls against one column.

<?php

declare(strict_types=1);

use PimBay\SearchQuery\Pimcore\SearchTerms\SearchTermsQuery;
use PimBay\SearchQuery\SearchTerms\SearchTermsConfig;
use PimBay\SearchQuery\SearchTerms\SearchTermsParser;

$config = new SearchTermsConfig();
$parsed = (new SearchTermsParser())->parse(['dog', 'hors*', '-cow'], $config);

(new SearchTermsQuery())->apply($listing, 'title', $parsed, $config);

Negated terms (-cow above) also keep records whose column is NULL, because column != 'cow' is NULL rather than TRUE for those records and WHERE keeps only TRUE — so the stricter reading silently drops every record with no value. Pass new SearchTermsConfig(ignoredTermsMatchNull: false) for that stricter reading.

Security note: $column (here and in applyString()) is interpolated directly into the generated SQL fragment — only the parsed values go through addConditionParam()'s bound parameters. Only ever pass a literal from your own code (or an allowlist you control); never pass a raw, unvalidated end-user string as $column, or it opens a SQL injection path through the column name itself.

Applying more than one search to the same column

AbstractListing::addConditionParam() keys its conditions by the SQL fragment string, so a second, identically shaped condition on the same column would silently replace the first instead of being added. apply()/applyString() detect that and throw Exception\DuplicateConditionException rather than let a filter disappear.

Pass a $tag when two searches on one column are what you actually want. It is appended as an inert SQL comment, which makes the fragments distinct:

$query = new SearchTermsQuery();

$query->applyString($listing, 'title', 'dog', $config, 'first');   // (title = ?) /* first */
$query->applyString($listing, 'title', 'cat', $config, 'second');  // (title = ?) /* second */

$tag is whitelisted against /^[\w.-]+$/ and throws Exception\InvalidConditionTagException otherwise — it lands in raw SQL, so it is not escaped.

There is no $paramPrefix parameter as in search-query-doctrine — addConditionParam() takes positional ? placeholders, so there is nothing to namespace; $tag does the disambiguating job instead.

Testing

composer test:83-pimcore11     # PHP 8.3 + pimcore/pimcore ^11.5
composer test:83-pimcore12     # PHP 8.3 + pimcore/pimcore ^12.0
composer test:84-pimcore12     # PHP 8.4 + pimcore/pimcore ^12.0
composer test:84-pimcore2026   # PHP 8.4 + pimcore/pimcore ^2026.0
composer test:85-pimcore2026   # PHP 8.5 + pimcore/pimcore ^2026.0
composer test:all              # all of the above
composer test:coverage         # php83-pimcore12 combo, --coverage-text
composer test:mutation         # infection — mutation testing, --min-msi=100 --min-covered-msi=100

Each combo runs in its own Docker image with dependencies baked in at build time. Requires Docker and Docker Compose locally.

tests/Unit/ only — the listing is a hand-written SpyListing fixture extending the real DataObject\Listing, not a PHPUnit mock and not a running Pimcore installation. See docs/DECISIONS.md for both.

PHPPimcore 11.xPimcore 12.xPimcore 2026.x
8.3✅✅—
8.4—✅✅
8.5——✅

Development Helpers

composer php:cs        # php-cs-fixer, --dry-run --diff (check only)
composer php:cs:fix    # same, applies the fix
composer php:stan      # phpstan analyse

Architecture & Decisions

Packages in the stack

PackageDescription
pimbay/search-queryFramework-agnostic contracts this package adapts Pimcore to — no datasource code of its own.
pimbay/search-query-doctrineAdapters over a Doctrine DBAL or ORM QueryBuilder — the sibling package for non-Pimcore projects.
pimbay/search-query-pimcoreThis package — adapters over a Pimcore listing.

License

Public domain — Unlicense

Created by Jan Sarmir · No conditions · No copyright

Bundled third-party dependencies and their licenses: docs/THIRD-PARTY-NOTICES.md.