-
-
Notifications
You must be signed in to change notification settings - Fork 14
feat: add Scout package - full-text search for Hypervel #339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Create the initial Scout package with directory structure, composer.json with meilisearch-php dependency, and abstract Engine class for search engine implementations.
Define the public API contract for searchable models, including methods for searching, indexing, and managing scout metadata.
Manages search engine instances using static caching for process-global reuse. Engines hold no request state and are safe to share across coroutines. Supports Meilisearch, Collection, and Null drivers with extensibility for custom drivers.
Fluent query builder for searchable models with support for filters (where, whereIn, whereNotIn), ordering, soft deletes, pagination, and lazy collections.
Provides full-text search capabilities to Eloquent models using registerCallback() for lifecycle hooks and Context API for coroutine-safe sync disabling. Uses Coroutine::defer() for async indexing after response.
SearchableScope adds searchable/unsearchable macros to the query builder for chunked batch operations. Events (ModelsImported, ModelsFlushed) are dispatched when models are indexed or flushed.
Includes driver, prefix, queue, chunk sizes, concurrency, soft delete settings, and Meilisearch configuration. Defaults to Coroutine::defer() for async indexing.
Registers EngineManager and MeilisearchClient, merges config, registers publishable config, and registers console commands.
No-op engine implementation for testing or temporarily disabling search without code changes.
Database-backed engine that filters results in memory. Useful for testing without requiring an external search service.
Full Meilisearch integration with search, pagination, index management, and generateTenantToken() helper for secure frontend direct search. Handles soft delete metadata filtering.
MakeSearchable and RemoveFromSearch jobs for queue-based indexing. RemoveableScoutCollection preserves Scout keys for already-deleted models.
- Use Hyperf\Paginator classes instead of non-existent Hypervel pagination - Use Hyperf\Tappable\tap function import (existing Hypervel pattern) - Remove use function imports for global helpers (class_uses_recursive, collect) - Fix console commands to use App\Models namespace convention
- Register Hypervel\Scout namespace in autoload psr-4 - Add hypervel/scout to replace section - Add ScoutServiceProvider to hypervel providers - Add meilisearch/meilisearch-php dependency - Fix SearchableTestModel property types - Add comprehensive NullEngineTest with 12 test cases
- Create ScoutTestCase with RefreshDatabase and RunTestsInCoroutine - Add SearchableModel and SoftDeletableSearchableModel test models - Add migrations for test tables - Write CollectionEngineTest with full feature tests - Fix SearchableScope to use Hyperf\Database\Model\Scope interface - Fix Searchable trait Collection type for makeSearchableUsing
Tests update, delete, search, paginate, map, mapIds, flush, createIndex, deleteIndex, updateIndexSettings, and soft delete metadata handling.
Tests query building, where/whereIn/whereNotIn constraints, ordering, soft delete handling, pagination, macros, and engine integration.
Tests engine resolution, static caching, custom drivers, default driver handling, and cache clearing.
Tests search builder creation, searchable array, scout keys, sync disabling, soft delete handling, and metadata management.
Tests Context isolation for sync disabling across concurrent coroutines, including nested coroutines and withoutSyncingToSearch.
Callbacks: - Add searchIndexShouldBeUpdated() check in saved callback - Add wasSearchableBeforeUpdate() check before unsearchable() - Add wasSearchableBeforeDelete() check in deleted callback - Add forceDeleted callback for force-deleted models - Add restored callback (forced update, no searchIndexShouldBeUpdated check) Queue dispatch: - Implement proper queue dispatch using MakeSearchable::dispatch() - Use onConnection() and onQueue() from model configuration Macros: - Fix collection macros to use $this->first() instead of captured $self - Remove unused $chunk parameter from collection macros (matches Laravel) Builder: - Add simplePaginateRaw() method for Laravel API parity - Add Arrayable support in whereIn/whereNotIn methods Tests: - Add tests for simplePaginateRaw, Arrayable support - Add tests for default return values of lifecycle methods
When queue.after_commit is enabled, Scout jobs are dispatched only after database transactions commit, preventing indexing of data that might be rolled back. Changes: - Add queue.after_commit config option (default false) - Chain ->afterCommit() on MakeSearchable/RemoveFromSearch jobs when enabled - Update README with queue configuration documentation - Add QueueDispatchTest with 8 test cases for queue behavior - Fix test config to use queue.after_commit (was at root level)
- Add TypesenseClient binding in ScoutServiceProvider (matches Meilisearch pattern) - Update EngineManager to resolve TypesenseClient from container - Improves testability and allows apps to override client configuration
SearchUsingPrefix attribute tests: - Verify prefix columns use 'query%' pattern instead of '%query%' - Verify non-prefix columns still use full wildcard - Add PrefixSearchableModel test fixture SearchableScope macro tests: - Verify searchable() dispatches ModelsImported event - Verify unsearchable() dispatches ModelsFlushed event - Verify shouldBeSearchable() filtering is applied - Verify custom chunk sizes work correctly - Verify query constraints are respected - Add ConditionalSearchableModel test fixture
The test claimed to verify that shouldBeSearchable() filtering works, but only asserted that the event contained all models. Now it actually verifies that only the 2 visible models (not the hidden one) appear in search results after the searchable() macro runs.
- Add tests/bootstrap.php for .env loading - Add .env.example with Meilisearch/Typesense settings - Add MeilisearchIntegrationTestCase base class with parallel-safe prefixes - Add TypesenseIntegrationTestCase base class with parallel-safe prefixes - Update phpunit.xml.dist with bootstrap reference - Update workflow: exclude integration group from main job, add dedicated jobs - Add .env to .gitignore
- MeilisearchEngineIntegrationTest: 13 tests for core CRUD, search, pagination - MeilisearchFilteringIntegrationTest: 6 tests for where/whereIn/whereNotIn - MeilisearchSortingIntegrationTest: 4 tests for orderBy operations - TypesenseEngineIntegrationTest: 10 tests for core operations - TypesenseFilteringIntegrationTest: 6 tests for filtering operations - TypesenseSearchableModel with typesenseCollectionSchema() and typesenseSearchParameters() - Updated base test cases to use setUpInCoroutine() for HTTP client initialization
The SCOUT_COMMAND path was using Concurrent::create() to spawn coroutines but never waited for them. Since console commands don't run inside a coroutine runtime by default, those coroutines were orphaned when the command exited. Changes: - Use WaitConcurrent instead of Concurrent for proper wait() support - ImportCommand wraps execution in run() when not already in coroutine - Add waitForSearchableJobs() called in finally block for cleanup - Add coroutine context checks with clear error messages - Restore concurrency config option for command parallelism control - Add command and soft-delete integration tests
- Remove RunTestsInCoroutine from base test cases in tests/Support - Add initializeMeilisearch/initializeTypesense methods for flexible client init - Scout test cases extend base classes and add RunTestsInCoroutine - Command tests use same test case (base Command wraps in coroutine) - Remove unnecessary run() wrapper from ImportCommand - Delete redundant command-specific test case files
- Rename concurrency to command_concurrency (explicit command-specific) - Fix TypesenseEngine to enforce maxTotalResults in search() and paginate() - Add ConfigTest for command_concurrency, chunk.searchable/unsearchable - Add MeilisearchIndexSettingsIntegrationTest for sync-index-settings - Add TypesenseConfigIntegrationTest for model-settings, max_total_results, import_action - Add ConfigBasedTypesenseModel for testing config-based Typesense settings
Remove unnecessary coroutine context checks - let code fail naturally if called outside expected context rather than adding explicit guards.
- Add scout:delete-all-indexes command for deleting all search indexes - Add PaginatesEloquentModels contract for custom engine pagination - Update Builder to check for pagination contracts before fallback - Add unit tests for DeleteAllIndexesCommand - Add integration test for delete-all-indexes with Meilisearch - Register new command in ScoutServiceProvider and test cases
- Add tests verifying Builder delegates to PaginatesEloquentModels interface - Add tests verifying Builder delegates to PaginatesEloquentModelsUsingDatabase interface - Fix import ordering in Builder.php (php-cs-fixer)
ImportCommand tests: - Model class resolution with fully qualified names - Model not found exception handling - App\Models namespace fallback resolution SyncIndexSettingsCommand tests: - Engine not supporting UpdatesIndexSettings returns failure - No index settings configured returns success with info - Settings sync success flow - Driver option override - Index name prefix resolution logic
Introduces Scout utility class with: - Static $makeSearchableJob and $removeFromSearchJob properties - makeSearchableUsing() and removeFromSearchUsing() setters - engine() convenience method for engine access - resetJobClasses() for test isolation Updates Searchable trait to dispatch jobs via Scout::$makeSearchableJob and Scout::$removeFromSearchJob, enabling users to customize job classes for logging, monitoring, retry behavior, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces Hypervel Scout, a comprehensive full-text search package adapted from Laravel Scout for Hypervel's Swoole-based coroutine environment. The package provides multiple search engine implementations (Meilisearch, Typesense, Database, Collection, Null) with automatic indexing, batch operations, soft delete support, and a fluent search builder API.
Key Changes:
- Full-text search infrastructure with coroutine-safe state management using
ContextAPI - Five search engine implementations with different use cases
- Test infrastructure supporting parallel test execution with unique prefixes
- Queue-based or deferred indexing with configurable concurrency
- Comprehensive test suite including unit, feature, and integration tests
Reviewed changes
Copilot reviewed 90 out of 91 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/bootstrap.php | Test bootstrap for loading .env configuration |
| tests/Support/*.php | Base test cases for Meilisearch and Typesense integration tests with parallel safety |
| tests/Scout/migrations/*.php | Database migrations for test models |
| tests/Scout/Models/*.php | Test model implementations for various scenarios |
| tests/Scout/Unit/*.php | Unit tests for core Scout functionality |
| tests/Scout/Feature/*.php | Feature tests for search engines and coroutine safety |
| tests/Scout/Integration/*.php | Integration tests for Meilisearch and Typesense |
| src/scout/src/Searchable.php | Core trait providing search capabilities with coroutine-safe state |
| src/scout/src/SearchableScope.php | Global scope adding batch search macros to query builder |
| src/scout/src/Scout.php | Utility class for job customization and engine access |
| src/scout/src/Jobs/*.php | Queue job implementations for async indexing |
| src/scout/src/Exceptions/*.php | Scout-specific exception classes |
| src/scout/src/Events/*.php | Event classes for import/flush operations |
| src/scout/src/ScoutServiceProvider.php | Service provider for Scout registration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 90 out of 91 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
This PR introduces Hypervel Scout, a full-text search package for Eloquent models ported from Laravel Scout and adapted for Hypervel's Swoole-based coroutine environment.
Key Features
Search Engines
Core Functionality
searchable()andunsearchable()macros on query builders and collectionswithTrashed()/onlyTrashed()where(),whereIn(),whereNotIn(),orderBy(),take(), paginationgetScoutKey()/getScoutKeyName()shouldBeSearchable()Hypervel-Specific Adaptations
ModelObserverclass with static stateregisterCallback()withContextCoroutine::defer()by defaultWaitConcurrentparallel execution$syncingDisabledForContext::set()/Context::get()config('scout.queue')booleanqueue.enabled+queue.after_commit$enginesarrayAdditional Features Not in Laravel Scout
SearchableInterfacecontract - Explicit interface for type-safe searchable modelscommand_concurrencyconfig - Control parallel coroutine count during bulk imports (default: 50)Features Not Ported from Laravel Scout
scout:queue-importcommand - Replaced byWaitConcurrentparallel processing in standard importMakeRangeSearchablejob - Range-based queue import replaced by concurrent coroutine processingArchitecture
Usage
Basic Setup
Searching
Commands
Configuration
Key configuration options in
config/scout.php:Engine-Specific Helpers
MeilisearchEngine includes a
generateTenantToken()convenience method for multi-tenant applications. This implements Meilisearch's recommended multi-tenancy pattern where all tenants share a single index and tenant tokens enforce data isolation at query time:This is a driver-specific helper, not a core Scout feature.
Testing Considerations
collectiondriver for unit tests (in-memory, no external dependencies)nulldriver to disable search entirely in test environmentswithoutSyncingToSearch()is coroutine-safe for isolating test casesEngineManager::forgetEngines()to reset between testsChanges vs Laravel Scout
SearchableInterfacein addition to using theSearchabletrait. Required for proper type safety / PHPStan level 5queue.enabledinstead of a simplequeuebooleansyncWithSearchUsing()returnsnullby default instead of falling back to default queue connectionRelated Documentation
src/scout/config/scout.php