Add Scout lifecycle and filter maintenance APIs - #466
Conversation
Add replacement-based, boot-time callbacks for builder preparation, searchable documents, index settings, and model-wide flush guards. Keep callback state bounded to four nullable slots, reset it through Scout::flushState(), and share constants between Scout's job defaults and their reset path. Cover unset behavior, complete callback arguments, replacement semantics, and state cleanup.
Resolve and prepare the selected engine at each outer Builder terminal while leaving Builder construction and configuration side-effect free. Keep pagination preparation single-shot by retaining raw engine resolution in the internal total-count path. Cover result terminals, pagination terminals, ordering, and the no-work construction boundary.
Run the Scout model-flush callback against the resolved engine before deleting an entire model index. Add an optional force argument without changing the default call shape. Mark the explicit scout:flush command as forced while keeping scout:import --fresh on the guarded default path. Cover callback ordering and both command behaviors.
Invoke the Scout settings lifecycle after application and soft-delete settings are assembled and after the physical index name is known. Support both model-backed and raw named settings entries, including callbacks that turn an empty configured entry into concrete settings. Cover command inputs, ordering, nullable model context, and the final index target.
Introduce the optional DeletesByFilter engine capability and implement it for Algolia with prepared builders, explicit write-target selection, empty-filter refusal, missing-index handling, and verified task completion. Preserve caller-authored filters when Builder constraints are present by composing both expressions with explicit precedence. Run final documents through Scout's lifecycle boundary and cover search, pagination, callbacks, deletion failures, and the real service path.
Compose application filters with Builder constraints across string and array filter forms, prepare final documents, and add completion-aware filtered deletion with bounded polling and precise missing-index handling. Replace network key discovery in token generation with explicit parent-key identity and local signing so credentials cannot mix identifiers and secrets. Cover option precedence, filter shapes, task failures, timeouts, target selection, and real-service signing and deletion.
Compose raw Typesense filter_by expressions with Builder constraints while retaining their precedence, and pass final indexed documents and lazy-created schemas through Scout lifecycle preparation. Implement synchronous filtered deletion with prepared builders, explicit write-target selection, empty-filter refusal, and missing-collection handling. Cover search callbacks, schema authority, target selection, failure paths, and the real service behavior.
Require Algolia index deletion waits to return a published terminal task so swallowed polling failures cannot let setup or teardown race unfinished cleanup. Limit Meilisearch's shared pending-task waiter to indexes owned by the current test prefix, preventing parallel workers from adopting each other's tasks. Add deterministic coverage for incomplete Algolia results and prefix-isolated Meilisearch waits.
Give the standalone types analysis its own repository-local cache directory, matching the main PHPStan configuration's local-cache policy. This prevents cached paths from deleted worktrees from leaking into types-only analysis and keeps the two configurations from sharing incompatible result state.
Describe the lifecycle registration points, Builder preparation timing, document and settings callbacks, model-flush guards, and the optional filtered-deletion capability. Document raw-filter composition, completion guarantees, explicit Meilisearch token credentials, and forced command flushes. Keep the package README limited to concise, actionable differences from Laravel Scout.
Capture the completed lifecycle, filter-composition, filtered-deletion, explicit token-signing, and external-service verification contracts in a focused design record. Update the prior Scout and framework lifecycle plans to reflect truthful Algolia completion checks and per-worker Meilisearch task ownership.
|
Warning Review limit reached
Next review available in: 6 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughChangesScout lifecycle and filter maintenance
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds boot-time Scout lifecycle callbacks and completion-aware filtered deletion for Algolia, Meilisearch, and Typesense.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/scout/src/Builder.php | Routes search and pagination terminals through a single Builder-preparation boundary while preserving unprepared internal count resolution. |
| src/scout/src/Scout.php | Adds replacement-based worker-lifetime callbacks for Builder, document, settings, and model-flush lifecycles with state cleanup. |
| src/scout/src/Engines/AlgoliaEngine.php | Adds document preparation, grouped filter composition, and completion-aware filtered deletion. |
| src/scout/src/Engines/MeilisearchEngine.php | Adds lifecycle preparation, array/string filter composition, bounded filtered-deletion waiting, and explicit local tenant-token signing. |
| src/scout/src/Engines/TypesenseEngine.php | Adds lifecycle preparation, grouped filter composition, synchronous filtered deletion, and prepared lazy collection schemas. |
| src/scout/src/Searchable.php | Adds the optional force argument and invokes the model-flush lifecycle guard before engine flushing. |
| src/foundation/src/Testing/Concerns/InteractsWithAlgolia.php | Requires published terminal results for every scheduled Algolia cleanup task. |
| src/foundation/src/Testing/Concerns/InteractsWithMeilisearch.php | Restricts pending-task waits to indexes owned by the current test-worker prefix. |
Reviews (3): Last reviewed commit: "Await unscoped Meilisearch test index cr..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/scout/src/Scout.php (2)
33-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider typed class constants for the job-class defaults.
DEFAULT_MAKE_SEARCHABLE_JOBandDEFAULT_REMOVE_FROM_SEARCH_JOBhold class-string values but declare no type. PHP 8.3+ supports typed class constants for scalar types, includingstring. Add thestringtype to catch accidental type mismatches at class-load time instead of at runtime.♻️ Proposed typed constants
- protected const DEFAULT_MAKE_SEARCHABLE_JOB = MakeSearchable::class; + protected const string DEFAULT_MAKE_SEARCHABLE_JOB = MakeSearchable::class; - protected const DEFAULT_REMOVE_FROM_SEARCH_JOB = RemoveFromSearch::class; + protected const string DEFAULT_REMOVE_FROM_SEARCH_JOB = RemoveFromSearch::class;As per coding guidelines,
src/**/*.phprequires "Use modern PHP 8.4+, declare strict types in every file, and provide native types wherever permitted."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/scout/src/Scout.php` around lines 33 - 38, Update the DEFAULT_MAKE_SEARCHABLE_JOB and DEFAULT_REMOVE_FROM_SEARCH_JOB class constants in Scout to explicitly declare the string type, preserving their existing MakeSearchable::class and RemoveFromSearch::class values.Source: Coding guidelines
111-114: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse first-class callable syntax for consistency with the rest of the file set.
Each
*Usingregistration method converts the incomingcallablewithClosure::fromCallable($callback).Builder::query()in this same PR already uses the first-class callable syntax$callback(...)for the identical purpose. Both forms are functionally equivalent since PHP 8.1. Align the four Scout registration methods with the newer, terser syntax already used in this codebase.♻️ Proposed change (repeat for all four methods)
- static::$prepareBuilderCallback = Closure::fromCallable($callback); + static::$prepareBuilderCallback = $callback(...);Also applies to: 134-137, 161-164, 188-191
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/scout/src/Scout.php` around lines 111 - 114, Update all four Scout *Using registration methods, including prepareBuilderUsing and the methods at the other referenced locations, to assign the incoming callable via first-class callable invocation syntax like $callback(...) instead of Closure::fromCallable($callback), matching Builder::query() while preserving their existing callback registration behavior.src/scout/src/Engines/MeilisearchEngine.php (1)
38-43: 🚀 Performance & Scalability | 🔵 TrivialConfirm the blocking wait budget for
deleteByFilter.
FILTER_DELETE_TIMEOUT_IN_MSis 500_000, sowaitForTask()can block the calling coroutine for up to 500 seconds.deleteByFilter()is reachable from request-serving code paths, not only from console commands. Consider making the timeout configurable, or document that callers must run filtered deletion from a queue or console context.Also applies to: 429-434
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/scout/src/Engines/MeilisearchEngine.php` around lines 38 - 43, Review the blocking wait in deleteByFilter and avoid hard-coding a 500-second request-path delay: make FILTER_DELETE_TIMEOUT_IN_MS configurable through the existing engine configuration mechanism, or explicitly document and enforce that deleteByFilter callers run only in queue/console contexts. Preserve the FILTER_DELETE_INTERVAL_IN_MS polling behavior and ensure the chosen timeout policy applies to every deleteByFilter waitForTask call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plans/2026-08-02-1749-scout-lifecycle-and-filter-maintenance-apis.md`:
- Line 165: Update the validation sequence in the documented lifecycle plan to
include PHPStan for source changes and the full parallel test suite before
signoff, while preserving the existing changed-test, focused-suite, composer
fix, diff-check, and review gates. Also require running the Testbench suite
whenever Testbench changes are present.
In `@src/foundation/src/Testing/Concerns/InteractsWithAlgolia.php`:
- Around line 144-151: Update the deletion-task loop in the Algolia teardown
flow to record the first non-published result as a failure, continue calling
waitForTask for every scheduled task, and rethrow the recorded failure only
after the loop completes. Preserve the existing failure message and add a
regression covering two indices where the first task is incomplete, verifying
both tasks are awaited before failure is raised.
In `@tests/Integration/Scout/Meilisearch/MeilisearchEngineIntegrationTest.php`:
- Around line 60-70: Move the assertNotNull checks for $uid and $secret into the
try block that handles the Meilisearch key lifecycle, keeping key creation and
identifier extraction before it. Ensure cleanup via deleteKey($uid) still
executes when either assertion fails.
In `@tests/Scout/Unit/Engines/MeilisearchEngineTest.php`:
- Around line 124-138: Update the test cleanup flow to clear Scout lifecycle
callbacks and static state between tests: ensure
AfterEachTestSubscriber::flushStateAfterTest() invokes Scout::flushState(), or
register Scout::flushState() through AfterEachTestCleanup::flushUsing() after
the reset. Apply this to the cleanup mechanism covering
Scout::prepareSearchableDocumentUsing registrations so callbacks cannot leak
into later tests.
In `@tests/Scout/Unit/Engines/TypesenseEngineTest.php`:
- Around line 604-616: Update the Builder filter merge used by delete/search
parameter construction so the model-level filter_by from
typesenseSearchParameters() is preserved and combined with the option-supplied
filter_by, rather than overwritten. Ensure the resulting expression includes
both filters with correct grouping, and update affected deleteByFilter/search
parameter test expectations accordingly.
---
Nitpick comments:
In `@src/scout/src/Engines/MeilisearchEngine.php`:
- Around line 38-43: Review the blocking wait in deleteByFilter and avoid
hard-coding a 500-second request-path delay: make FILTER_DELETE_TIMEOUT_IN_MS
configurable through the existing engine configuration mechanism, or explicitly
document and enforce that deleteByFilter callers run only in queue/console
contexts. Preserve the FILTER_DELETE_INTERVAL_IN_MS polling behavior and ensure
the chosen timeout policy applies to every deleteByFilter waitForTask call.
In `@src/scout/src/Scout.php`:
- Around line 33-38: Update the DEFAULT_MAKE_SEARCHABLE_JOB and
DEFAULT_REMOVE_FROM_SEARCH_JOB class constants in Scout to explicitly declare
the string type, preserving their existing MakeSearchable::class and
RemoveFromSearch::class values.
- Around line 111-114: Update all four Scout *Using registration methods,
including prepareBuilderUsing and the methods at the other referenced locations,
to assign the incoming callable via first-class callable invocation syntax like
$callback(...) instead of Closure::fromCallable($callback), matching
Builder::query() while preserving their existing callback registration behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f3d7a238-d19e-4673-8d52-15312bd74ce8
📒 Files selected for processing (33)
docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.mddocs/plans/2026-08-02-1006-scout-current-parity-queue-and-search-lifecycles.mddocs/plans/2026-08-02-1749-scout-lifecycle-and-filter-maintenance-apis.mdphpstan.types.neon.distsrc/boost/docs/scout.mdsrc/foundation/src/Testing/Concerns/InteractsWithAlgolia.phpsrc/foundation/src/Testing/Concerns/InteractsWithMeilisearch.phpsrc/scout/README.mdsrc/scout/src/Builder.phpsrc/scout/src/Console/FlushCommand.phpsrc/scout/src/Console/IndexCommand.phpsrc/scout/src/Console/SyncIndexSettingsCommand.phpsrc/scout/src/Contracts/DeletesByFilter.phpsrc/scout/src/Engines/AlgoliaEngine.phpsrc/scout/src/Engines/MeilisearchEngine.phpsrc/scout/src/Engines/TypesenseEngine.phpsrc/scout/src/Scout.phpsrc/scout/src/Searchable.phptests/Foundation/Testing/Concerns/ExternalServiceOptInTest.phptests/Integration/Scout/Algolia/AlgoliaFilteringIntegrationTest.phptests/Integration/Scout/Meilisearch/MeilisearchEngineIntegrationTest.phptests/Integration/Scout/Meilisearch/MeilisearchFilteringIntegrationTest.phptests/Integration/Scout/Typesense/TypesenseFilteringIntegrationTest.phptests/Scout/Feature/SearchableModelTest.phptests/Scout/Unit/BuilderTest.phptests/Scout/Unit/Console/FlushCommandTest.phptests/Scout/Unit/Console/ImportCommandTest.phptests/Scout/Unit/Console/IndexCommandTest.phptests/Scout/Unit/Console/SyncIndexSettingsCommandTest.phptests/Scout/Unit/Engines/AlgoliaEngineTest.phptests/Scout/Unit/Engines/MeilisearchEngineTest.phptests/Scout/Unit/Engines/TypesenseEngineTest.phptests/Scout/Unit/ScoutTest.php
Wait for every already-scheduled Algolia index deletion when the SDK returns an incomplete normal result, then report the first incomplete task. Preserve immediate propagation for SDK failures so retry exhaustion is not multiplied across indexes. Add two-index regressions for both completion boundaries, and ensure Meilisearch integration child keys are removed whenever either supported deletion identifier is available without masking nullable-response assertions. Document the asynchronous deletion behavior, Meilisearch wait bound, and recommended execution context for long filtered deletions.
|
@coderabbitai Thanks for the review. Follow-up is pushed in 64221b6. Changed:
Reviewed without changes:
The full repository gate and the configured Meilisearch integration suite are green. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --stat --oneline --decorate --no-renames 64221b6cb
git diff 64221b6cb^ 64221b6cb -- \
src/foundation/src/Testing/Concerns/InteractsWithAlgolia.php \
src/foundation/src/Testing/Concerns/InteractsWithMeilisearch.php \
src/boost/docs/scout.md \
tests/Foundation/Testing/Concerns/ExternalServiceOptInTest.php
printf '\n--- Matching follow-up test and documentation references ---\n'
rg -n -C 3 \
'waitForTask|published|incomplete|delete.*key|apiKey|tenant token|filtered deletion|asynchronous|timeout' \
src/foundation/src/Testing/Concerns/InteractsWithAlgolia.php \
src/foundation/src/Testing/Concerns/InteractsWithMeilisearch.php \
src/boost/docs/scout.md \
tests/Foundation/Testing/Concerns/ExternalServiceOptInTest.php || trueLength of output: 30935 🏁 Script executed: #!/bin/bash
set -euo pipefail
git diff 64221b6cb^ 64221b6cb -- \
tests/Integration/Scout/Meilisearch/MeilisearchEngineIntegrationTest.php
printf '\n--- Relevant Meilisearch integration cleanup context ---\n'
ast-grep outline tests/Integration/Scout/Meilisearch/MeilisearchEngineIntegrationTest.php --items all
rg -n -C 8 \
'delete.*key|deleteKey|apiKey|uid|finally|tearDown|assert|expect' \
tests/Integration/Scout/Meilisearch/MeilisearchEngineIntegrationTest.phpLength of output: 13922
The reported review findings are addressed. 🐇 ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! You are interacting with an AI system. |
Capture and wait for the exact task returned when the scoped-deletion regression creates its deliberately unprefixed index. The shared task waiter correctly ignores indexes outside the current test prefix, so relying on it left the precondition vulnerable to Meilisearch asynchronous creation timing. Keep the prefix-scoped waiter unchanged for parallel isolation and make the real-service command regression deterministic on slower CI workers.
This PR adds first-class Scout lifecycle hooks and completion-aware filter maintenance for the external search engines.
Scout previously had no common boundary for an application to adjust a Builder, final document, or final index settings after Scout had assembled its own data but before the engine performed I/O. Raw engine filters and Builder constraints could also replace one another, and deleting documents by a filter required direct SDK calls with different completion behavior for every engine.
What changed
Compatibility and runtime behavior
The existing Laravel Scout search APIs and call shapes remain unchanged. The removeAllFromSearch force argument is additive and defaults to the previous behavior.
Normal searches add one callback null check at the terminal boundary and local string composition when both filter sources are present. Indexing and settings updates add one callback null check at their existing assembly boundary. No callback registry, retained per-request map, engine wrapper, cache, lock, or extra network lookup is introduced.
Testing
The change includes unit and feature coverage for callback registration, replacement and cleanup; every Builder terminal; command flush behavior; settings preparation; filter composition; filtered-deletion targets and failure paths; and explicit Meilisearch credential signing.
The Algolia, Meilisearch, and Typesense integration suites exercise composed searches and filtered deletion against the real services. The full formatter, static-analysis, parallel test, Testbench, and dogfood gates pass.
The Scout guide and package README document the new public behavior and the actionable differences from Laravel Scout.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation