symkit / search-bundle
A standalone search bundle for Symfony applications.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/symkit/search-bundle
Requires
- php: >=8.2
- symfony/config: ^7.0 || ^8.0
- symfony/dependency-injection: ^7.0 || ^8.0
- symfony/framework-bundle: ^7.0 || ^8.0
- symfony/http-kernel: ^7.0 || ^8.0
Requires (Dev)
- deptrac/deptrac: ^2.0
- friendsofphp/php-cs-fixer: ^3.0
- infection/infection: ^0.29
- nyholm/symfony-bundle-test: ^3.0
- phpro/grumphp: ^2.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.0 || ^11.0
- symfony/phpunit-bridge: ^7.0 || ^8.0
- symfony/ux-live-component: ^2.0
- symfony/ux-twig-component: ^2.0
- symkit/bundle-ai-kit: ^0.0.3
Suggests
- symfony/asset-mapper: Required for AssetMapper integration (Stimulus controllers).
- symfony/translation: Required for translatable search UI strings.
- symfony/ux-icons: Required for rendering icons in the search UI.
- symfony/ux-live-component: Required for the GlobalSearch live component UI.
- symfony/ux-twig-component: Required for the GlobalSearch Twig component.
README
A modern, memory-efficient global search bundle for Symfony applications. Supports multiple named search engines, each with its own providers (or shared providers), and includes a ready-to-use UI built with Symfony UX Live Components and Tailwind CSS.
Features
- Multi-engine: Define multiple search engines (
main,admin, ...) with independent or shared providers. - Memory Efficient: Uses PHP Generators (
yield) to handle large result sets without memory spikes. - SOLID Architecture: Decoupled providers, engines, and services with a Contract-first design.
- Events:
PreSearchEventandPostSearchEventfor query modification, result filtering, analytics. - Ready-to-use UI: Accessible global search modal built with Symfony UX Live Components and Tailwind CSS.
- Keyboard Navigation:
Cmd+K/Ctrl+Kto open, arrow keys to navigate,Enterto select. - Multi-category: Group results by category (Pages, Media, Routes, etc.) with custom priorities.
Requirements
- PHP 8.2+
- Symfony 7.0+ or 8.0+
Required for the UI component
- Tailwind CSS for styling
symfony/ux-live-componentandsymfony/ux-twig-componentfor the GlobalSearch componentsymfony/ux-iconsfor rendering iconssymfony/asset-mapperfor Stimulus controller auto-discovery
The UI is optional. The search API works without any of the above.
Installation
composer require symkit/search-bundle
Install optional dependencies for the UI:
composer require symfony/ux-live-component symfony/ux-twig-component symfony/ux-icons
Configure Assets (ImportMap)
Register the Stimulus controller in your importmap.php:
return [ // ... 'search/global-search-modal_controller' => [ 'path' => 'search/global-search-modal_controller.js', ], ];
Tailwind CSS Integration
Add the bundle's templates to your Tailwind scan:
@import "tailwindcss"; @source "../../vendor/symkit/search-bundle/templates";
Configuration
Single engine (default)
With no configuration, a single default engine is created with UI enabled:
# config/packages/symkit_search.yaml symkit_search: ~
Multiple engines
Define named engines, each with its own ui toggle:
# config/packages/symkit_search.yaml symkit_search: default_engine: main # explicit default (falls back to first declared) engines: main: ui: true # GlobalSearch component enabled admin: ui: false # API only, no UI component
- If
enginesis omitted, a singledefaultengine withui: trueis created. default_enginesets which engine is aliased toSearchServiceInterface. Falls back to the first declared engine.- Setting
engines: []disables all search functionality.
Usage
1. Create a Search Provider
Implement SearchProviderInterface and use the #[AsSearchProvider] attribute to assign it to an engine.
Shared provider (all engines):
use Symkit\SearchBundle\Attribute\AsSearchProvider; use Symkit\SearchBundle\Contract\SearchProviderInterface; use Symkit\SearchBundle\Model\SearchResult; #[AsSearchProvider] final readonly class PageSearchProvider implements SearchProviderInterface { public function search(string $query): iterable { // yield SearchResult instances... } public function getCategory(): string { return 'Pages'; } public function getPriority(): int { return 10; } }
Engine-specific provider:
#[AsSearchProvider(engine: 'admin')] final readonly class UserSearchProvider implements SearchProviderInterface { // Only available in the "admin" engine }
Via YAML tags (alternative to the attribute):
services: App\Search\MyProvider: tags: - { name: symkit_search.provider, engine: main }
Without the engine attribute, a provider is registered in all engines.
2. Add the UI Component
Include the GlobalSearch Live Component in your Twig layout:
{# Default engine #} {{ component('GlobalSearch') }} {# Specific engine #} {{ component('GlobalSearch', { engine: 'admin' }) }}
The component handles:
- Keyboard Shortcuts:
Cmd+K/Ctrl+Kto open. - Debounced Search: Optimized typing experience.
- Results Grouping: Grouped by category, sorted by priority.
- Accessibility: ARIA roles,
aria-livefor screen readers.
3. Use the Search API Directly
Inject the default engine or the registry:
use Symkit\SearchBundle\Contract\SearchServiceInterface; use Symkit\SearchBundle\Contract\SearchEngineRegistryInterface; final readonly class MyController { public function __construct( private SearchServiceInterface $search, // default engine private SearchEngineRegistryInterface $registry, // all engines ) {} public function __invoke(): Response { // Default engine $results = $this->search->search('hello'); // Specific engine $adminResults = $this->registry->get('admin')->search('hello'); } }
4. Listen to Search Events
Hook into the search lifecycle with PreSearchEvent and PostSearchEvent:
use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symkit\SearchBundle\Event\PreSearchEvent; use Symkit\SearchBundle\Event\PostSearchEvent; #[AsEventListener] final readonly class SearchAnalyticsListener { public function __invoke(PostSearchEvent $event): void { // Log searches, filter results, add scoring, etc. } }
PreSearchEvent lets you modify or cancel the query before providers are called. PostSearchEvent lets you filter, reorder, or enrich the results.
Architecture
graph TD
Config["symkit_search.engines"] --> Pass["SearchProviderPass"]
Tags["#[AsSearchProvider] tags"] --> Pass
Pass --> E1["SearchService (main)"]
Pass --> E2["SearchService (admin)"]
E1 --> R["SearchEngineRegistry"]
E2 --> R
R --> UI["GlobalSearch Component"]
UI -->|"engine='main'"| E1
UI -->|"engine='admin'"| E2
E1 -.->|dispatch| PreSearch["PreSearchEvent"]
E1 -.->|dispatch| PostSearch["PostSearchEvent"]
Loading
Advanced Customization
Search Result Model
| Parameter | Type | Description |
|---|---|---|
title |
string |
The main text displayed for the result. |
subtitle |
string |
Secondary information (e.g., path, category). |
url |
string |
The destination link when clicked. |
icon |
string |
ux_icon identifier (e.g., heroicons:photo). |
badge |
?string |
Optional badge text (e.g., status). |
Overriding Templates
Customize the search modal UI by creating:
templates/bundles/SymkitSearchBundle/components/GlobalSearch.html.twig
Dedicated Exceptions
The bundle throws EngineNotFoundException (extends InvalidArgumentException) when requesting an unknown engine, making it easy to catch bundle-specific errors.
Contributing
make install # Install dependencies make install-hooks # Install git hooks make quality # Run full quality pipeline (cs + phpstan + deptrac + tests + infection)