Add centralized batch processing infrastructure - #16
Conversation
Lead with pain points and stakes instead of features. Reframe technical capabilities as user outcomes. Reduce Getting Started steps. Consistent structure across all Analyze ecosystem modules: headline with stakes, blockquote with problem/solution, "You need this if" with real scenarios, "What You Get" with outcomes.
Introduce BatchableAnalyzerInterface for opt-in batch support, centralized AnalyzeBatchService, batch form, and Drush command so all analyzers can be batch-controlled uniformly via GUI and CLI. Resolves #15
Add processEntity() and hasResults() methods to AIBrandVoiceAnalyzer, enabling the centralized Analyze batch system to process brand voice analysis across content entities via admin UI and Drush CLI. Refs #12 Depends on dxpr/analyze#16
Add processEntity() and hasResults() methods to the security audit analyzer plugin, enabling integration with the centralized batch processing system in the Analyze base module. Refs: #11, dxpr/analyze#16
Replace the module-specific batch implementation with the centralized BatchableAnalyzerInterface from the Analyze module. This adds processEntity() and hasResults() methods, enabling the plugin to participate in the unified batch system via both Admin UI and Drush CLI. Refs #15, dxpr/analyze#16
Use -W flag when installing phpcompatibility/php-compatibility to resolve version conflict with squizlabs/php_codesniffer v4 required by drupal/coder 9.0.
Add processEntity() and hasResults() methods to the PostHog analyzer plugin, enabling batch pre-fetching of analytics data via the Analyze module's centralized batch system (admin UI and Drush CLI). Also adds README.md and updates project description with batch feature. Refs: #1, dxpr/analyze#16
The stable phpcompatibility/php-compatibility releases only support squizlabs/php_codesniffer v3, which conflicts with drupal/coder 9.0 (requires phpcs v4). Use dev-develop branch which has v4 support.
Multi-line function declarations must have each parameter on its own line with trailing comma and closing parenthesis on a separate line.
- AnalyzeCommandsBase with switchToAdmin() and YAML output helpers - AnalyzeSetupCommands (analyze:setup-ai) with --host and --check - AI skill files for Claude Code and cross-agent tools - hook_requirements() for stale skill file detection - E2E test suite with helpers, batch and setup-ai tests - Docker Compose e2e-test service - GitHub Actions E2E workflow - drush.services.yml for Drush 12+ DI registration - Updated README.md and project description with CLI & AI docs
Code Review: #16Reviewed the full diff (1,710+/141- across 25 files), the parent issue #15, and the three established cross-project precedents (dxpr/dxpr_theme_helper#47, dxpr/dxpr_builder#4468, dxpr/rl#32). Critical:
|
|
Review notes:
|
Architecture Review — Senior Drupal PerspectiveI've reviewed this PR in the context of issue #15 and the 5 dependent submodule PRs. The interface design ( Critical1.
|
Live Testing Results — dxpr-cms-2026-01-07 (Drupal 11.3.1, PHP 8.3, MySQL)All 6 modules on FATAL — Command completely broken out of the boxF1.
|
| Test | Result |
|---|---|
analyze:batch --list |
Lists all 5 batch-capable analyzers correctly |
analyze:batch --limit=2 |
Discovers entities, paginates correctly, processes exactly 2 |
analyze:batch --force --limit=2 |
Re-processes already-analyzed entities |
analyze:batch --analyzers=analyze_ai_brand_voice_analyzer --limit=1 |
Filters to single analyzer |
analyze:batch --types=node:landing_page --limit=1 --force |
Filters to specific bundle |
analyze:batch --types=node:page → "No entities found" |
Correctly skips unpublished content |
analyze:batch --analyzers=nonexistent |
Graceful error: "Unknown analyzer" |
analyze:batch --types=node:nonexistent |
Graceful warning: "No selected analyzers are enabled" |
analyze:batch --types=invalidformat |
Graceful warning, no crash |
analyze:setup-ai |
Installs all 3 skill files correctly |
analyze:setup-ai --check |
Reports "up to date" after install, "NOT INSTALLED" before |
analyze:setup-ai --host=claude |
Installs only Claude skill file |
analyze:setup-ai --host=agents |
Installs only agents skill files |
analyze:broken-links:report |
Works correctly after enabling module |
Batch form route /admin/config/content/analyze-batch |
Route exists and form class is loadable |
FAIL / ISSUES
| Test | Issue |
|---|---|
| Full batch run (all analyzers, no limit) | AI rate limiting floods with no backoff — 25+ rate limit errors logged. All entities counted as "processed" despite failures |
analyze:broken-links:scan |
Command deleted but analyze:broken-links:report still references it in "no data" message |
core:requirements --filter=analyze after deleting skill files |
No warning shown — analyze_requirements() only checks for stale files (hash mismatch), not missing files |
| Old per-module batch routes | All 4 old batch routes still accessible at /admin/config/analyze/{module}/batch with no deprecation notice |
| Drupal 11.3 deprecation | Calling Renderer::render with NULL is deprecated in drupal:11.3.0 logged during batch processing |
Summary
The analyze:batch command is completely non-functional as shipped due to two fatal bugs (uninitialized variable + render context). After patching those 10 lines across 8 files, the core functionality works well — entity discovery, pagination, filtering, and progress tracking all behave correctly. The analyze:setup-ai command works flawlessly.
The processEntity() → renderSummary() delegation pattern is the root cause of the render context issue and should be reconsidered for CLI/batch contexts.
- Catch AiRateLimitException per-analyzer with exponential backoff (2s, 4s, 8s, up to 3 retries before giving up) - Track processed/failed/rate_limited counts separately - Report honest success/failure counts instead of always "success" - Move try/catch inside analyzer loop so one analyzer failure doesn't skip remaining analyzers for the same entity
- Add --status flag showing analysis coverage per bundle via fast DB counts (no entity loading/rendering) - Add countAnalyzedEntities() to BatchableAnalyzerInterface - Per-entity progress lines: [1/50] node 123 ... OK/FAILED - Confirmation prompt with cost warning before processing - Fix O(N) entity load: use chunked loadMultiple() with cache reset - Catch exceptions in hasResults() to handle missing view_builders
- Add switchBack() to AnalyzeCommandsBase, call it on all exit paths - Check copy() and mkdir() return values in installFiles()
- Document all 3 interface methods including countAnalyzedEntities() - Add key rules: return FALSE on failure, don't catch rate limits, use renderPlain() not render(), fast DB count for status - Add --status to command table
Move countAnalyzedEntities() from BatchableAnalyzerInterface to AnalyzePluginBase with a default return of 0. Analyzers that persist results to DB can override it; those that fetch data on-demand (PostHog, Search Console) inherit the default. Third-party analyzers implementing BatchableAnalyzerInterface are not affected — the interface still only requires processEntity() and hasResults().
|
Two remaining bugs after the latest follow-up commits:
|
- Remove AiRateLimitException import; detect rate limits by class name string check to avoid hard dependency on drupal/ai - Use method_exists() for countAnalyzedEntities() since it's on the base class, not the interface - Update README to reference renderInIsolation (not renderPlain)
|
Re-checking after the latest follow-up commits, I still don't think the batch/status logic is fully addressed:
|
- processBatch() now only runs analyzers enabled for each entity's bundle (checks analyze.settings config per entity) - runWithBackoff() returns the bool from processEntity() so FALSE (analysis failed) is properly counted as a failure - Remove unused phpstan-ignore comment and unused variable
|
There is still dead duplication in the new cleanup work.
I want to push back pretty hard on this kind of drift: every extra helper and every unused path increases maintenance cost for zero product value. Please either wire the new helper in and delete the superseded code, or delete the helper. We should not merge redundant abstractions into a codebase that is explicitly trying to get smaller and cleaner. |
ProjectRootHelper was added but never wired in. getModulePath() was never called. Both are dead code adding maintenance cost.
Summary
BatchableAnalyzerInterfacefor opt-in batch capability on analyzer plugins (follows Drupal core'sConfigurableInterfacepattern)AnalyzeBatchServicereplacing per-module batch service boilerplate (~95% duplicate code across modules)AnalyzeBatchFormat/admin/config/content/analyze-batchwith multi-analyzer selectiondrush analyze:batchcommand with--analyzers,--types,--limit,--force,--listoptionsTest plan
drush analyze:batch --listshows batch-capable analyzersdrush analyze:batch --analyzers=<id> --types=node:articleprocesses entities/admin/config/content/analyze-batchrenders correctly--forceflag re-processes already-analyzed entitiesResolves #15