ibexa/solr
Ibexa Solr search engine bundle for Ibexa DXP, providing the Solr search backend implementation. Includes unit and integration test setup instructions for contributors.
This bundle is not a library you call directly — it plugs a Solr backend into Ibexa's SearchService abstraction (ibexa/core). Day-to-day you keep writing $searchService->findContent(new LocationQuery(...)) and only touch this package for configuration, indexing, and extending the translation layer.
Minimal path:
composer require ibexa/solr
Register the bundle in config/bundles.php (Ibexa\Solr\IbexaSolrBundle) and load its routing/services if your install doesn't auto-wire it.ibexa/solr image or your own Solr 9.8+ node. The core schema/config the bundle expects lives in this package under src/lib/Resources/config/solr/ (schema.xml, solrconfig.xml, stopwords/synonyms/protwords). Copy them into the Solr server's core config directory — do not hand-edit the vendor copy.config/packages/ibexa_solr.yaml or via env):
ibexa_solr:
connections:
default:
# or use SOLR_DSN / SOLR_CORE env vars
dsn: '%env(SOLR_DSN)%' # e.g. http://solr:8983/solr
core: '%env(SOLR_CORE)%' # e.g. collection1
http_client:
timeout: 30
max_retries: 3
php bin/console ibexa:solr:create-core
ibexa:
system:
default:
search:
engine: solr
php bin/console ibexa:reindex --iteration-limit=500
Then run any existing front-end query (a LocationQuery with Criterion\FullText and a facet) and diff results against the legacy engine.Where to look first in the repo: DependencyInjection/ (all config keys and service tags), Resources/config/solr/schema.xml (field naming conventions such as *_txt, *_s, *_id, *_b, *_dt, *_l, *_geolocation), the criterion/sort-clause visitor directories, and the field-mapper directory. Those four areas answer 90% of "why is my query behaving differently" questions.
Read path — stay inside the Search API. Criteria, sort clauses and facets you already use are translated to Solr syntax by visitors. Notable ones this bundle provides: FullText, ContentName, Image, IsBookmarked, IsUserEnabled, IsContainer, MapLocationDistance, plus date metadata variants. If a criterion works on the legacy engine but returns wrong results on Solr, the visitor is the first suspect, not your query.
Write path — event-driven indexing. Content publish/update/delete events are picked up by the indexer; there is no manual "add to index" step in normal code. For bulk loads, disable live indexing (defer via the indexer/--no-interaction style batching or your own queue), then run ibexa:reindex --iteration-limit=N in chunks so you don't blow memory or spam Solr commits.
Custom criterion pattern. This is the most common real extension:
final class MyFlag extends Criterion { public function __construct(public string $value) {} }
final class MyFlagVisitor extends CriterionVisitor
{
public function canVisit(CriterionInterface $criterion): bool
{ return $criterion instanceof MyFlag; }
public function visit(CriterionInterface $criterion, ?CriterionVisitor $subVisitor = null): string
{ return sprintf('my_flag_s:"%s"', addcslashes($criterion->value, '"\\')); }
}
Register with the criterion-visitor tag used by the bundle (since IBX-9650 these are collected through a tagged iterator — check Resources/config/*.yaml in the installed version for the exact tag name, which was rebranded in 5.x), then clear cache. Add a sort-clause visitor in the same way if you need ordering.
Custom field type pattern. Indexing, filtering, sorting, faceting and spellchecking all depend on the Solr field existing with the right dynamic-field suffix. Three coordinated changes:
schema.xml or rely on a matching dynamic field (e.g. *_txt, *_s) — safest is to mirror the naming convention so the dynamic rule applies.Facets and aggregations. Use the aggregation API (TermAggregation, RangeAggregation, CountryTermAggregation, field-relation variants) rather than deprecated Facets. Custom aggregations need their own visitor + result extractor, same registration pattern as criteria. Note the recent fix for CountryTermAggregation ISO/IDC code collisions — bump to ≥4.6.30/5.0.8 if you aggregate countries.
Embeddings / vector search. Newer releases add dense-vector dynamic fields keyed by dimension suffix and an embedding visitor, so you can index an embedding per content and query by vector. Treat as additive: it only works if your schema includes the vector field type and you control the embedding generation pipeline yourself.
Multi-core / SolrCloud. CORES_SETUP=single is the simple path. multi maps per-language cores via SOLR_CORE_<LANGUAGE>-style env vars and an endpoint resolver; searches are fanned out across cores and merged. Pick this only if you have genuine language-scale volume — it doubles your indexing and debugging cost.
CI pattern. Copy the integration-test recipe from the README: unit tests run with php vendor/bin/phpunit --bootstrap tests/bootstrap.php; integration tests need a live Solr with CORES_SETUP=single and run against vendor/ibexa/core/phpunit-integration-legacy-solr.xml. Make the host configurable (IBX-5133 added that) so it works in containers.
Local development. Run Solr in Docker alongside the app, mount the schema/config from the vendor path, and keep a second, throwaway core you can delete/recreate freely — you will be doing it often.
schema.xml is full of dynamicField wildcards, a misnamed field often doesn't error — it just never matches. If a filter silently returns everything or nothing, inspect the generated query with debug=query in the Solr admin UI.autoSoftCommit/autoCommit in solrconfig.xml govern how quickly new content appears. Don't add sleep() to your feature tests — tune commit settings or commit explicitly in tests.max_results/offset behaviour means large offset values throw or truncate. Keep pagination bounded or design around it; don't assume offset=50000 works because the legacy engine allowed it._root_, [child] filters). Sorting or filtering on Location fields behaves differently from content fields, and "wrong count per location" bugs are nearly always a nesting issue._language_id and language-specific text fields are what make Criterion\LanguageCode and analysis work. A query without a language scope can return surprising or empty results across siteaccesses.+ - && || ! ( ) { } [ ] ^ " ~ * ? : \ /) break queries. Use the provided full-text criterion for free text rather than concatenating raw strings; if you build a visitor, escape with addcslashes and quote phrases.canVisit() that never matches because you checked against an interface instead of the concrete criterion. The bundle itself moved from Criterion type hints to CriterionInterface (IBX-8534) — follow that.SOLR_CORE yields 404s that surface as generic search exceptions. Basic-auth credentials are now redacted from error output (IBX-11617) — so if you were relying on seeing them in logs, you won't; check the connection config instead. Timeouts/retries are configurable per connection (IBX-3863); raise retries for bulk reindex, lower them for web requests.NOASSERTION in metadata is not "public domain".doc.ibexa.co, and upstream issue trackers are behind the Ibexa support portal. Budget time to read DependencyInjection/Configuration.php and the service YAMLs directly when something is undocumented.ibexa:reindex --verbose for indexing failures, Solr admin UI → Analysis for analyzer/stopword/synonym problems (these live in Solr config files, not PHP, so they need a core reload), and Solr debug=query to see the exact translated query. When a query is wrong, log both the Ibexa Criterion tree and the emitted Solr string — the gap between them is always a visitor.How can I help you explore Laravel packages today?