Skip to content

Implement CLEANUP.md fixes: security, DI, routing, streaming, concurrency, and generator improvements#4

Open
vedavith wants to merge 15 commits into
mainfrom
feature/forge-multi-tenant
Open

Implement CLEANUP.md fixes: security, DI, routing, streaming, concurrency, and generator improvements#4
vedavith wants to merge 15 commits into
mainfrom
feature/forge-multi-tenant

Conversation

@vedavith
Copy link
Copy Markdown
Owner

@vedavith vedavith commented Jun 5, 2026

Summary

  • Filter suspended tenants from bulk migrations (allActive())
  • Validate column names in BaseRepository to prevent SQL injection
  • Add --dry-run mode to migrate, migrate:rollback, migrate:all-tenants
  • Validate tenant ID format in TenantService::onboard()
  • Add DI container with reflection-based auto-wiring
  • Add FK and index support to MigrationBuilder
  • Make SubdomainTenantResolver minimum host parts configurable
  • Replace custom router with FastRoute (parameterised routes, 405 responses)
  • Add Response::stream(callable) for chunk-based streaming
  • Enforce RequestLifecycle in TenantContext to prevent silent tenant leaks
  • Add --parallel N worker support to migrate:all-tenants via symfony/process
  • Update ARCHITECTURE.md and CONCEPT.md to reflect all changes

Test plan

  • Run vendor/bin/phpunit — 234 tests, 1 skipped (Request::capture CLI skip), 0 failures
  • Verify migrate:all-tenants --dry-run produces [DRY RUN] prefixed output
  • Verify migrate:all-tenants --parallel 3 spawns concurrent subprocesses
  • Verify parameterised routes resolve $request->param() correctly

vedavith added 15 commits June 5, 2026 07:30
…ng, provisioning rollback

- Route tenant:create through TenantService.onboard() so tenant is registered in DB
- Add provisioning rollback: drop tenant DB on migration failure
- Add migrate:all-tenants command for keeping existing tenant DBs in sync
- Complete HTTP layer: Pipeline middleware runner, Router, Request::path(), Response value object (withJson/send/immutable)
- Add SubdomainTenantResolver and wire into TenantResolverFactory
- Add RequestLifecycle helper for concurrency safety (Swoole/RoadRunner)
- Add tenant suspend/resume/offboard to TenantService + TenantRepository
- Add connection registry to TenantConnectionResolver (reuse PDO per DB)
- Run CoreSchemaManager for both tenancy strategies (always create tenants table)
- Add --config-dir option to generate:all command
- Expand ConfigValidator to require all database.* keys
- 185 tests passing, 1 skipped (getallheaders unavailable in CLI SAPI)
- ARCHITECTURE.md: rewrite covering all current components — Pipeline, Router,
  SubdomainTenantResolver, RequestLifecycle, MigrateAllTenantsCommand, tenant
  lifecycle, connection pooling, HTTP layer, boot sequence, concurrency notes
- CONCEPT.md: expand from stub to full glossary of every major class and concept
- CLEANUP.md: document known limitations and deferred work — parameterised routes,
  DI container, migration dry-run, tenant ID validation, SQL injection notes
TenantRepository gains allActive() which selects only status='active'
rows. MigrateAllTenantsCommand switches from all() to allActive() so
suspended tenants are excluded from bulk migration runs.
assertColumnName() enforces ^[a-zA-Z0-9_]+$ on every column name
passed to create(), where(), and update() before it is interpolated
into SQL. Throws InvalidArgumentException on violation.
MigrationRunner::run() and rollback() accept a $dryRun flag. In dry-run
mode all writes are skipped and output is prefixed with [DRY RUN].
migrate, migrate:rollback, and migrate:all-tenants gain a --dry-run
option wired through to the runner.
onboard() now rejects tenant IDs that do not match ^[a-zA-Z0-9_-]+$
before touching the database. TenantRepository and TenantProvisioner
can be injected via constructor, eliminating the need for overload:
mocks in tests.
Container supports bind(), singleton(), instance(), and make(). Auto-
wiring resolves typed constructor parameters recursively. Application
creates a container on boot, registers TenantRepository, TenantProvisioner,
and TenantService as singletons, and exposes it via getContainer().
MigrationBuilder emits CONSTRAINT ... FOREIGN KEY from relations.belongsTo
and INDEX / UNIQUE INDEX from indexes. SchemaValidator validates both
new sections. EntitySchema exposes getIndexes().
subdomainDepth + 3 hardcoded threshold replaced with a configurable
minParts parameter (default 3). Set tenancy.subdomain_min_parts: 2 in
application.yaml to support two-part hosts like acme.io.
TenantResolverFactory reads the new config key.
Router delegates to nikic/fast-route for route matching. {name} segments
are resolved to named captures available via Request::param() and params().
Method mismatches now return 405 instead of 404. Exact routes registered
before parameterised ones take priority.
Response::stream(callable) sends status and headers then delegates
output to the caller. The callable echoes chunks and controls flush
timing, avoiding full-body buffering for large responses.
TenantContext::setTenantId() throws LogicException if a tenant is already
set, turning silent tenant leaks into hard errors. Applications using
RequestLifecycle::begin() correctly are unaffected as begin() calls
clear() before each request.
--parallel N (default 5) spawns N concurrent subprocesses via
symfony/process, each running migrate:all-tenants --tenant=<id>.
Cross-platform: works on Linux, macOS, and Windows. --dry-run is
forwarded to subprocesses automatically. Also adds nikic/fast-route
dependency used by the HTTP router.
CLEANUP.md updated to reflect all completed fixes. Orphaned root-level
scripts run-generator.php and test.php removed.
Documents: DI container, column name validation, tenant ID validation,
parameterised routes with FastRoute, response streaming, dry-run mode,
parallel migrate:all-tenants workers, configurable subdomain min parts,
FK and index generator support, TenantContext lifecycle enforcement,
and updated CLI command table with key options.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant