Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Luigi's Box Bundle
https://github.com/answear/luigis-box-bundle
Admin
Luigi's Box Bundle is a Symfony integration package that enables developers to integrate Luigi's Box
...
Tokens:
8,499
Snippets:
42
Trust Score:
6.8
Update:
1 month ago
Context
Skills
Chat
Benchmark
89.2
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Luigi's Box Bundle Luigi's Box Bundle is a Symfony integration package that provides seamless access to the Luigi's Box search and content management platform. It enables PHP developers to index, update, search, and manage content through Luigi's Box API with type-safe value objects, automatic authentication handling, and comprehensive response parsing. The bundle supports multiple configurations for different environments or accounts, offering features like full and partial content updates, content removal, advanced search with facets and filters, update-by-query batch operations, and product recommendations. All API interactions are handled through dependency-injected services with proper exception handling for rate limiting, bad requests, and service unavailability. ## Installation Install the bundle via Composer. ```bash composer require answear/luigis-box-bundle ``` ## Configuration Configure the bundle with your Luigi's Box API credentials and optional timeout settings. ```yaml # config/packages/answear_luigis_box.yaml answear_luigis_box: default_config: primary configs: primary: host: 'https://live.luigisbox.com' # default publicKey: 'your_public_key' privateKey: 'your_private_key' connectionTimeout: 4.0 # default requestTimeout: 10.0 # default searchTimeout: 6.0 # default searchCacheTtl: 0 # max 300 seconds recommendationsRequestTimeout: 1.0 # default recommendationsConnectionTimeout: 1.0 # default secondary: publicKey: 'another_public_key' privateKey: 'another_private_key' ``` ## Switching Between Configurations Switch between multiple Luigi's Box configurations at runtime using the ConfigProvider service. ```php <?php use Answear\LuigisBoxBundle\Service\ConfigProvider; use Answear\LuigisBoxBundle\DTO\ConfigDTO; class ProductSyncService { public function __construct( private ConfigProvider $configProvider, ) {} public function syncToSecondaryAccount(): void { // Switch to a different configuration $this->configProvider->setConfig('secondary'); // Add dynamic configuration at runtime $this->configProvider->addConfig('temporary', new ConfigDTO( publicKey: 'temp_public_key', privateKey: 'temp_private_key', host: 'https://live.luigisbox.com', connectionTimeout: 4.0, requestTimeout: 10.0, searchTimeout: 6.0, searchCacheTtl: 60, recommendationsRequestTimeout: 1.0, recommendationsConnectionTimeout: 1.0, )); // Set custom headers for requests $this->configProvider->setHeader('X-Custom-Header', 'custom-value'); // Reset all custom headers $this->configProvider->resetHeaders(); } } ``` ## Full Content Update Create or replace complete content documents in Luigi's Box index with all fields and nested items. ```php <?php use Answear\LuigisBoxBundle\Service\RequestInterface; use Answear\LuigisBoxBundle\ValueObject\ContentUpdate; use Answear\LuigisBoxBundle\ValueObject\ContentUpdateCollection; use Answear\LuigisBoxBundle\Exception\BadRequestException; use Answear\LuigisBoxBundle\Exception\TooManyItemsException; use Answear\LuigisBoxBundle\Exception\TooManyRequestsException; class ProductIndexer { public function __construct( private RequestInterface $request, ) {} public function indexProducts(array $products): void { $updates = []; foreach ($products as $product) { $contentUpdate = new ContentUpdate( title: $product->getName(), url: '/products/' . $product->getSlug(), type: 'product', fields: [ 'title' => $product->getName(), 'description' => $product->getDescription(), 'price' => $product->getPrice(), 'brand' => $product->getBrand(), 'category' => $product->getCategory(), 'availability' => $product->isAvailable() ? 1 : 0, 'image_url' => $product->getImageUrl(), ] ); // Add nested variants $nestedVariants = []; foreach ($product->getVariants() as $variant) { $nestedVariants[] = new ContentUpdate( title: $variant->getName(), url: '/products/' . $product->getSlug() . '/variant/' . $variant->getId(), type: 'variant', fields: [ 'size' => $variant->getSize(), 'color' => $variant->getColor(), 'sku' => $variant->getSku(), ] ); } $contentUpdate->setNested($nestedVariants); $updates[] = $contentUpdate; } $collection = new ContentUpdateCollection($updates); try { $response = $this->request->contentUpdate($collection); if ($response->isSuccess()) { echo "All {$response->okCount} documents indexed successfully.\n"; } else { echo "Indexed: {$response->okCount}, Errors: {$response->errorsCount}\n"; foreach ($response->errors as $error) { echo "Failed URL: {$error->url}, Reason: {$error->reason}\n"; } } } catch (TooManyItemsException $e) { echo "Batch too large. Limit: {$e->limit} items.\n"; } catch (TooManyRequestsException $e) { echo "Rate limited. Retry after {$e->retryAfterSeconds} seconds.\n"; } catch (BadRequestException $e) { echo "Bad request: " . $e->response->getBody() . "\n"; } } } ``` ## Partial Content Update Update specific fields of existing documents without replacing the entire document. ```php <?php use Answear\LuigisBoxBundle\Service\RequestInterface; use Answear\LuigisBoxBundle\ValueObject\PartialContentUpdate; use Answear\LuigisBoxBundle\ValueObject\ContentUpdateCollection; class ProductUpdater { public function __construct( private RequestInterface $request, ) {} public function updatePrices(array $priceChanges): void { $updates = []; foreach ($priceChanges as $productUrl => $newPrice) { $updates[] = new PartialContentUpdate( url: $productUrl, type: 'product', fields: [ 'price' => $newPrice, 'price_updated_at' => (new \DateTime())->format('Y-m-d H:i:s'), ] ); } $collection = new ContentUpdateCollection($updates); $response = $this->request->partialContentUpdate($collection); echo "Updated {$response->okCount} prices, {$response->errorsCount} failures.\n"; } } ``` ## Content Removal Remove documents from the Luigi's Box index by URL and type. ```php <?php use Answear\LuigisBoxBundle\Service\RequestInterface; use Answear\LuigisBoxBundle\ValueObject\ContentRemoval; use Answear\LuigisBoxBundle\ValueObject\ContentRemovalCollection; class ProductRemover { public function __construct( private RequestInterface $request, ) {} public function removeProducts(array $productUrls): void { $removals = []; foreach ($productUrls as $url) { $removals[] = new ContentRemoval( url: $url, type: 'product' ); } $collection = new ContentRemovalCollection($removals); $response = $this->request->contentRemoval($collection); if ($response->isSuccess()) { echo "Removed {$response->okCount} products from index.\n"; } else { foreach ($response->errors as $error) { echo "Failed to remove {$error->url}: {$error->reason}\n"; if ($error->causedBy) { print_r($error->causedBy); } } } } } ``` ## Change Availability Quickly enable or disable product availability using a simplified partial update interface. ```php <?php use Answear\LuigisBoxBundle\Service\RequestInterface; use Answear\LuigisBoxBundle\ValueObject\ContentAvailability; use Answear\LuigisBoxBundle\ValueObject\ContentAvailabilityCollection; class InventorySync { public function __construct( private RequestInterface $request, ) {} public function updateAvailability(array $stockChanges): void { $availabilities = []; foreach ($stockChanges as $productUrl => $inStock) { $availabilities[] = new ContentAvailability( url: $productUrl, available: $inStock ); } // Batch update multiple products $collection = new ContentAvailabilityCollection($availabilities); $response = $this->request->changeAvailability($collection); echo "Updated availability for {$response->okCount} products.\n"; } public function disableSingleProduct(string $productUrl): void { // Single product update $availability = new ContentAvailability( url: $productUrl, available: false ); $response = $this->request->changeAvailability($availability); if ($response->isSuccess()) { echo "Product disabled successfully.\n"; } } } ``` ## Search Execute searches with filters, facets, sorting, preferences, and pagination using the SearchUrlBuilder. ```php <?php use Answear\LuigisBoxBundle\Service\SearchRequestInterface; use Answear\LuigisBoxBundle\ValueObject\SearchUrlBuilder; use Answear\LuigisBoxBundle\ValueObject\Search\Context; use Answear\LuigisBoxBundle\Exception\BadRequestException; use Answear\LuigisBoxBundle\Exception\ServiceUnavailableException; class ProductSearch { public function __construct( private SearchRequestInterface $searchRequest, ) {} public function search(string $query, int $page = 1, array $activeFilters = []): array { $urlBuilder = new SearchUrlBuilder(page: $page); $urlBuilder ->setQuery($query) ->setSize(20) ->addFilter('type', 'product') ->addFilter('availability', 1) ->setSort('relevance', 'desc') ->setFacets(['brand', 'category', 'color', 'size', 'price']) ->setDynamicFacetsSize(10) ->setHitFields(['title', 'price', 'image_url', 'brand']) ->setQuicksearchTypes(['product', 'category']) ->setFixits(true); // Apply active filters from user selection foreach ($activeFilters as $key => $values) { foreach ((array) $values as $value) { $urlBuilder->addFilter($key, $value); } } // Add brand preference to boost certain brands $urlBuilder->addPrefer('brand', 'Premium Brand'); $urlBuilder->addPrefer('brand', 'Featured Brand'); // Add must filters (required conditions) $urlBuilder->addMustFilter('in_stock', true); // Set user context for personalization $urlBuilder->setUserId('user-123'); $urlBuilder->setClientId('client-abc'); // Add geo-location context $context = new Context(); $context->setGeoLocation(52.2297, 21.0122); // Warsaw coordinates $context->setGeoLocationField('store_location'); $context->setAvailabilityField('local_availability'); $context->setBoostField('popularity_score'); $context->setFreshnessField('created_at'); $urlBuilder->setContext($context); // Enable query understanding (requires userId to be set first) $urlBuilder->enableQueryUnderstanding(); try { $response = $this->searchRequest->search($urlBuilder); return [ 'query' => $response->query, 'corrected_query' => $response->correctedQuery, 'total_hits' => $response->totalHits, 'current_size' => $response->currentSize, 'guid' => $response->guid, 'filters' => $response->filters, 'hits' => array_map(fn($hit) => [ 'url' => $hit->url, 'type' => $hit->type, 'title' => $hit->attributes['title'] ?? '', 'price' => $hit->attributes['price'] ?? 0, 'highlight' => $hit->highlight, 'exact_match' => $hit->exact, 'alternative' => $hit->alternative, 'nested' => $hit->nested, ], $response->hits), 'quicksearch_hits' => array_map(fn($hit) => [ 'url' => $hit->url, 'title' => $hit->attributes['title'] ?? '', ], $response->quickSearchHits), 'facets' => array_map(fn($facet) => [ 'name' => $facet->name, 'type' => $facet->type, 'values' => $facet->values, ], $response->facets), ]; } catch (BadRequestException $e) { throw new \RuntimeException('Search failed: ' . $e->getMessage()); } catch (ServiceUnavailableException $e) { throw new \RuntimeException('Search service unavailable'); } } } ``` ## Update By Query Perform batch updates on documents matching specific search criteria without knowing individual URLs. ```php <?php use Answear\LuigisBoxBundle\Service\UpdateByQueryRequestInterface; use Answear\LuigisBoxBundle\ValueObject\UpdateByQuery; use Answear\LuigisBoxBundle\ValueObject\UpdateByQuery\Search; use Answear\LuigisBoxBundle\ValueObject\UpdateByQuery\Update; class BulkProductUpdater { public function __construct( private UpdateByQueryRequestInterface $updateByQueryRequest, ) {} public function updateProductColors(): int { // Define search criteria - find all products with color "green" $search = new Search( types: ['product'], fields: ['color' => 'green'] ); // Define the update - change color to multiple values $update = new Update( fields: ['color' => ['olive', 'emerald', 'forest']] ); $updateByQuery = new UpdateByQuery($search, $update); // Start the async update job $response = $this->updateByQueryRequest->update($updateByQuery); $jobId = $response->jobId; echo "Update job started with ID: {$jobId}\n"; // Poll for completion return $this->waitForCompletion($jobId); } private function waitForCompletion(int $jobId): int { $maxAttempts = 30; $attempt = 0; while ($attempt < $maxAttempts) { $statusResponse = $this->updateByQueryRequest->getStatus($jobId); echo "Job {$statusResponse->trackerId}: "; if ($statusResponse->isCompleted()) { echo "Completed!\n"; echo "Updated: {$statusResponse->okCount}, Errors: {$statusResponse->errorsCount}\n"; if ($statusResponse->errors) { foreach ($statusResponse->errors as $error) { echo "Error for {$error->url}: {$error->reason}\n"; } } return $statusResponse->okCount; } echo "Still processing...\n"; sleep(2); $attempt++; } throw new \RuntimeException("Job {$jobId} did not complete in time"); } } ``` ## Recommendations (Experimental) Fetch personalized product recommendations using various recommendation strategies. ```php <?php use Answear\LuigisBoxBundle\Service\RecommendationsRequestInterface; use Answear\LuigisBoxBundle\ValueObject\Recommendation; use Answear\LuigisBoxBundle\ValueObject\RecommendationsCollection; class RecommendationService { public function __construct( private RecommendationsRequestInterface $recommendationsRequest, ) {} public function getRecommendations(string $userId, array $currentItemIds): array { $recommendation = new Recommendation( recommendationType: 'similar_products', userId: $userId, hitFields: ['title', 'price', 'image_url', 'brand'], itemIds: $currentItemIds, blacklistedItemIds: ['product/discontinued-123'], size: 10, recommendationContext: [ 'page_type' => 'product_detail', 'category' => 'electronics', ], settingsOverride: [ 'diversity' => 0.5, ], markFallbackResults: true, recommenderClientIdentifier: 'web-app-v2', ); $collection = new RecommendationsCollection([$recommendation]); $response = $this->recommendationsRequest->getRecommendations($collection); if ($response->isSuccess()) { return $response->rawResponse; } return []; } } ``` ## Exception Handling Handle all possible API exceptions with appropriate error recovery strategies. ```php <?php use Answear\LuigisBoxBundle\Service\RequestInterface; use Answear\LuigisBoxBundle\ValueObject\ContentUpdateCollection; use Answear\LuigisBoxBundle\Exception\BadRequestException; use Answear\LuigisBoxBundle\Exception\TooManyItemsException; use Answear\LuigisBoxBundle\Exception\MalformedResponseException; use Answear\LuigisBoxBundle\Exception\TooManyRequestsException; use Answear\LuigisBoxBundle\Exception\ServiceUnavailableException; class ResilientContentSync { public function __construct( private RequestInterface $request, ) {} public function syncWithRetry(ContentUpdateCollection $collection, int $maxRetries = 3): bool { $attempt = 0; while ($attempt < $maxRetries) { try { $response = $this->request->contentUpdate($collection); if ($response->isSuccess()) { return true; } // Log partial failures but consider it success if some passed foreach ($response->errors as $error) { error_log("Sync error for {$error->url}: {$error->type} - {$error->reason}"); if ($error->causedBy) { error_log("Caused by: " . json_encode($error->causedBy)); } } return $response->okCount > 0; } catch (BadRequestException $e) { // Request is malformed, don't retry error_log("Bad request - check payload: " . $e->request->getBody()); error_log("Response: " . $e->response->getBody()); return false; } catch (TooManyItemsException $e) { // Split the batch and retry error_log("Batch too large. Max items: {$e->limit}"); return $this->syncInBatches($collection, $e->limit); } catch (TooManyRequestsException $e) { // Rate limited - wait and retry $waitTime = $e->retryAfterSeconds; error_log("Rate limited. Waiting {$waitTime} seconds..."); sleep($waitTime); $attempt++; } catch (MalformedResponseException $e) { // API returned unexpected response error_log("Malformed response: " . json_encode($e->response)); $attempt++; sleep(1); } catch (ServiceUnavailableException $e) { // Service is down - exponential backoff $waitTime = pow(2, $attempt); error_log("Service unavailable. Retrying in {$waitTime} seconds..."); sleep($waitTime); $attempt++; } } return false; } private function syncInBatches(ContentUpdateCollection $collection, int $batchSize): bool { // Implementation to split collection into smaller batches return true; } } ``` ## Summary Luigi's Box Bundle provides a complete toolkit for integrating Luigi's Box search and content management into Symfony applications. The primary use cases include e-commerce product indexing with full and partial updates, real-time inventory availability synchronization, advanced product search with faceted filtering and personalization, batch content updates using query-based operations, and AI-powered product recommendations. The bundle handles all authentication, request signing, and response parsing automatically. Integration follows standard Symfony patterns with dependency injection for all services. Configure your API credentials in YAML, inject `RequestInterface` for content operations, `SearchRequestInterface` for search, `UpdateByQueryRequestInterface` for batch updates, and `RecommendationsRequestInterface` for recommendations. The bundle supports multiple configurations for multi-tenant or multi-environment setups, making it suitable for complex e-commerce architectures with separate staging and production Luigi's Box accounts.