answear/luigis-box-bundle
Symfony bundle for integrating Luigi's Box search and content updates. Configure public/private keys, hosts, timeouts, cache TTL, multiple or dynamic configs and custom headers, then send content updates and search requests through simple services.
Verdict: A small, purpose-built API client wrapped as a Symfony bundle. The reusable value is the DTO/response mapping layer (ValueObject, Response, Service namespaces) covering Luigi's Box content updates, search, update-by-query, and (since 4.1.0) recommendations. The framework glue is thin and Symfony-specific, so the "Laravel package" framing in our intake is inaccurate — treat this as a Symfony library we would adapt, not adopt.
Architecture fit. The bundle sits at the edge of the domain: it maps our catalog objects to Luigi's Box documents (ContentUpdate, PartialContentUpdate, ContentRemoval, ContentAvailability) and maps search responses back into SearchResponse/Hit/Facet objects. That is a clean seam — an application-layer port. It does not own indexing policy, document schema, or sync orchestration; those remain ours. For a Laravel codebase the DI extension and configuration tree are dead weight, but they are separable: the request/response/value-object classes are framework-agnostic and only need Guzzle plus config values to run.
Integration feasibility. Composer-installable, MIT, PHP 8.2+ and Symfony 6/7 per 4.0/5.0.0. Multi-config support (configs, default_config, ConfigProvider::setConfig) and dynamic config (addConfig with ConfigDTO) are useful for multi-store/multi-locale setups. Transport is Guzzle since 2.4.0, which aligns with our existing HTTP stack. The friction is that a Symfony bundle expects symfony/http-kernel/dependency-injection/config and very likely framework-bundle; Laravel does not load bundles, and pulling Symfony's full framework kernel into a Laravel app to serve one API client is disproportionate.
Technical risk.
MalformedResponseException is our early-warning signal.ApiResponse::$success is false if any document fails, while $okCount/$errors describe partial success. Partial-failure reconciliation is application work the bundle does not do for us.TooManyItemsException::$limit, TooManyRequestsException::$retryAfterSeconds), MIT allows vendoring if upstream stalls.Key questions.
symfony/framework-bundle, or only the DI/config components? This determines whether we adapt in place or fork the core.searchCacheTtl (PSR-6/Symfony cache)? Can we point it at Redis, and is the 300s ceiling acceptable for our freshness expectations?Stack fit. Laravel 10–12 on PHP 8.2+ is compatible at the language level and shares Guzzle and Symfony 6/7 components with the bundle. The idiomatic fit is: a Laravel service provider binds a LuigisBoxClient interface; a thin adapter constructs ConfigProvider from config/luigisbox.php (mapping publicKey, privateKey, host, connectionTimeout, requestTimeout, searchTimeout, searchCacheTtl); domain code depends only on our interface, never on Answear\LuigisBoxBundle\*. Index writes go through queued jobs, not request handlers.
Migration path. Assume an incumbent search (SQL LIKE, Laravel Scout, or Elasticsearch). Stand up Luigi's Box in parallel: dual-write index updates via queued jobs, add a feature-flagged search driver behind our existing search abstraction, and compare relevance and latency on real queries before cutover. Keep the incumbent readable for at least one full reindex cycle so rollback is a flag flip, not a data migration.
Compatibility. Pin an exact major (5.x) and treat upgrades as planned work, not patch bumps — every prior major dropped PHP/Symfony floors. Verify Composer resolution against Laravel's pinned symfony/* versions before estimating. If framework-bundle conflicts, fork or vendor the ValueObject/Response/Service namespaces behind our own provider; MIT makes this legitimate and the surface is small. Confirm the release tag's provenance given the anomalous date.
Sequencing.
ConfigProvider manually, execute one real search and one content update against a sandbox index. Gate: does it run without the Symfony kernel?ContentUpdate/PartialContentUpdate/ContentRemoval batches; per-object-type batching within TooManyItemsException::$limit; availability toggles wired to stock/status.SearchUrlBuilder, cache layer honoring the ≤300s TTL, fallback driver.UpdateByQuery + getStatus as an internal admin tool for bulk merchandising fixes.Maintenance. Low routine churn, but plan one BC-breaking upgrade per year and budget ~1–3 days each for a Symfony-fluent engineer. If we fork, add upstream release watching and a quarterly diff against the tagged version. No database or infrastructure footprint beyond the app and its cache.
Support. No vendor SLA on the bundle; support comes from Luigi's Box for the API and from a community issue tracker for the code. We must own an internal runbook: key rotation, config validation at boot, index schema changes, and a documented escalation path to Luigi's Box support. One named backend owner is the realistic minimum.
Scaling. All calls are synchronous Guzzle requests — content updates must be queued and rate-shaped, never inline in web requests. Honor TooManyItemsException::$limit by chunking and TooManyRequestsException::$retryAfterSeconds via job backoff; the bundle provides the signals but not the retry policy. Search throughput is bounded by Luigi's Box latency and the ≤300s cache; for hot queries add an application-level cache or edge cache rather than relying on searchCacheTtl. Separate searchTimeout from requestTimeout (2.1.0) so slow indexing does not degrade search.
Failure modes.
success=false with non-zero okCount means the index is silently partial. Needs a reconciliation job that reads ApiResponse::$errors (url, type, reason, causedBy) and re-queues or dead-letters, plus a dashboard on errorsCount.ServiceUnavailableException, timeouts): must degrade to the fallback driver; the 300s cache ceiling means the cache absorbs only minutes, not an incident.MalformedResponseException spikes indicate the API changed under us — alert on rate, not just occurrence.BadRequestException exposes request/response; validate keys and host at deploy time rather than at first query.Ramp-up. A Symfony-experienced PHP engineer is productive in 1–2 days from the README alone; a Laravel-only engineer needs ~1 week including the adapter and exception mapping. The real cost is not code — it is index design: object types, URL conventions, field schema, and availability semantics, which is a 1–2 week collaborative effort with merchandising/catalog owners, followed by ongoing relevance tuning. Documentation beyond the README is essentially absent, so budget for writing our own internal integration notes as a deliverable, not an afterthought.
How can I help you explore Laravel packages today?