Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost
APP_CONFIG_CACHE_TYPE=sharded
APP_CONTAINER_COMPILED=bootstrap/cache/container.php
APP_CONTAINER_COMPILED_ACTIVATION=off

ROUTER_MATCHER=fused

Expand Down
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/.gitattributes export-ignore
/captainhook.json export-ignore
/composer.lock export-ignore
/plan.md export-ignore
/CODE_OF_CONDUCT.md export-ignore
/CONTRIBUTING.md export-ignore
/SECURITY.md export-ignore
Expand Down Expand Up @@ -34,7 +35,9 @@
/bin export-ignore
/bootstrap/cache/config/*.php export-ignore
/bootstrap/cache/console/*.php export-ignore
/bootstrap/cache/container.php export-ignore
/bootstrap/cache/modules.php export-ignore
/bootstrap/cache/optimize.php export-ignore
/bootstrap/cache/routes/*.php export-ignore
/database export-ignore
/resources export-ignore
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
/.route-cache-*
/.vscode/
/.windsurf/
/bootstrap/cache/container.php
/bootstrap/cache/modules.php
/bootstrap/cache/optimize.php
/database/*.sqlite
/vendor/
*~
composer.lock
plan.md
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ Existing files are preserved unless `--force` is supplied. Register application
commands explicitly in `routes/console.php`, schedules in `routes/schedule.php`,
and supervised workers in `routes/workers.php`.

For an intentionally static controller action, use a first-class callable such
as `Route::get('/reports', ReportsController::show(...))`; route caching converts
that safe form to a plain descriptor. Captured closures and instance handlers
remain supported through the general cached-handler path.

## Optional modules

A new project installs only the core runtime. Add or publish capabilities as the
Expand Down Expand Up @@ -118,7 +123,20 @@ php infbyte app:ready
```

`optimize` compiles configuration, route, command, schedule, and module metadata
so requests do less work. `optimize:clear` removes generated caches.
plus the eligible HTTP container graph so requests do less work. It publishes a
final optimize manifest only after the complete set is ready. `optimize:clear`
removes every generated artifact and leaves uncached execution available.

`APP_CONFIG_CACHE_TYPE=single` loads one snapshot; `sharded` loads configuration
namespaces on demand. Neither is universally faster: measure the application's
minimal, authenticated, and database-backed routes before selecting one.
Compiled-container activation remains `off` by default because small requests
may not amortize its boot cost; use `always` only for a measured deployment,
commonly a persistent worker with OPcache.

Webrick automatically selects the response emitter. A known deployment may set
`WEBRICK_EMITTER` to `fpm`, `frankenphp`, `lsapi`, `unit`, `swoole`,
`roadrunner`, or `workerman`; leave it unset when runtime detection is desired.

The included deployment helper validates writable runtime paths and builds the
same caches:
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "project",
"require": {
"php": ">=8.4",
"infocyph/foundation": "^1.2.1"
"infocyph/foundation": "^1.3"
},
"require-dev": {
"infocyph/phpforge": "dev-main@dev"
Expand Down
13 changes: 9 additions & 4 deletions config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@
| controls environment-aware definitions. "lazy_loading" defers supported
| services, while "request_scope" isolates request-lived entries.
|
| "compiled" may point to compiled container metadata. Debug tracing is
| disabled by default; "level" selects the trace detail when enabled.
| "compiled" selects the application-owned resolver artifact. Activation
| remains off unless a measured deployment explicitly selects "always".
| Debug tracing is disabled by default; "level" selects its detail.
|
| Alias/environment examples: `http` and `production`. Boolean switches use
| `true|false`. Compiled path example: `bootstrap/cache/container.php`.
| `true|false`. The compiled artifact defaults to
| `bootstrap/cache/container.php`; APP_CONTAINER_COMPILED may select another
| application-owned relative or absolute path.
| Compiled activation values: `off|always`.
| Trace levels: `off|node|info|warn|warning|error|verbose`.
|
*/
Expand All @@ -57,7 +61,8 @@
'environment' => env('APP_ENV', 'local'),
'lazy_loading' => env('APP_CONTAINER_LAZY_LOADING', false),
'request_scope' => env('APP_CONTAINER_REQUEST_SCOPE', true),
'compiled' => env('APP_CONTAINER_COMPILED'),
'compiled' => env('APP_CONTAINER_COMPILED', 'bootstrap/cache/container.php'),
'compiled_activation' => env('APP_CONTAINER_COMPILED_ACTIVATION', 'off'),
'debug_tracing' => [
'enabled' => env('APP_CONTAINER_DEBUG_TRACING', false),
'level' => env('APP_CONTAINER_DEBUG_TRACE_LEVEL', 'node'),
Expand Down
4 changes: 3 additions & 1 deletion tests/Feature/FrameworkRuntimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,12 @@ function expectedNotifierClass(Application $app): string
$registered = [];

foreach ($router->routes() as $route) {
$registered[$route->getMethod() . ' ' . $route->getPath()] = true;
$registered[$route->getMethod() . ' ' . $route->getPath()] = $route->getHandler();
}

expect(array_keys($registered))->toBe(['GET /api/health', 'GET /json']);
expect($registered['GET /api/health'])->toBeInstanceOf(Closure::class)
->and($registered['GET /json'])->toBeInstanceOf(Closure::class);

$http = $app->testing()->http();
$health = $http->get('/api/health')
Expand Down
63 changes: 63 additions & 0 deletions tests/Feature/RouteCacheCliTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
declare(strict_types=1);

use Composer\InstalledVersions;
use App\Http\Controllers\SystemController;
use Infocyph\Foundation\Auth\AuthManager;
use Infocyph\Foundation\Database\DatabaseManager;
use Infocyph\Foundation\Foundation;
use Infocyph\Foundation\Messaging\MessagingManager;
use Infocyph\Foundation\Routing\RouteCachePath;
use Infocyph\Foundation\Session\SessionManager;
use Infocyph\Webrick\Request\Request;
use Infocyph\Webrick\Router\Matching\FusedMatcher;

it('uses the environment application name and reports the Foundation runtime version', function (): void {
$root = dirname(__DIR__, 2);
Expand Down Expand Up @@ -52,6 +59,62 @@
expect(is_file($cacheFile))->toBeTrue();
expect(filesize($cacheFile))->toBeGreaterThan(0);

$matcher = FusedMatcher::make()->enableCache($cacheFile);
[$cachedRoute] = $matcher->match('GET', 'localhost', '/json');
$cachedHandler = $cachedRoute->getHandler();
$webrickVersion = InstalledVersions::getVersion('infocyph/webrick') ?? '0.0.0';
$usesNativeHandler = version_compare($webrickVersion, '3.3.0', '>=');
if ($usesNativeHandler) {
expect($cachedHandler)->toBe([SystemController::class, 'json'])
->and(class_exists(\Opis\Closure\Serializer::class, false))->toBeFalse();
} else {
expect($cachedHandler)->toBeInstanceOf(Closure::class);
}

$runtime = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR)
. '/infbyte-cached-runtime-' . bin2hex(random_bytes(5));
$runtimeCache = $runtime . '/bootstrap/cache/routes/fused.php';
mkdir(dirname($runtimeCache), 0775, true);
mkdir($runtime . '/routes', 0775, true);
copy($cacheFile, $runtimeCache);
file_put_contents(
$runtime . '/routes/missing.php',
"<?php\n\nthrow new RuntimeException('Cached dispatch loaded route source.');\n",
);

try {
$app = Foundation::web([
'base_path' => $runtime,
'_config_cache' => false,
'router' => [
'cache' => true,
'files' => ['missing.php'],
'matcher' => 'fused',
],
]);
$repository = $app->container()->getRepository();
$response = $app->handle(Request::fake(method: 'GET', uri: 'http://localhost/json'));

expect($response->getStatusCode())->toBe(200)
->and((string) $response->getBody())->toContain('memory')
->and($repository->hasResolvedSingleton(AuthManager::class))->toBeFalse()
->and($repository->hasResolvedSingleton(SessionManager::class))->toBeFalse()
->and($repository->hasResolvedSingleton(DatabaseManager::class))->toBeFalse()
->and($repository->hasResolvedSingleton(MessagingManager::class))->toBeFalse()
->and(get_included_files())->not->toContain($runtime . '/routes/missing.php');
} finally {
if (isset($app)) {
$app->container()->unset();
}
unlink($runtimeCache);
rmdir(dirname($runtimeCache));
rmdir(dirname(dirname($runtimeCache)));
rmdir(dirname(dirname(dirname($runtimeCache))));
unlink($runtime . '/routes/missing.php');
rmdir($runtime . '/routes');
rmdir($runtime);
}

[$clearExitCode, $clearOutput] = runInfbyteCommand([
PHP_BINARY,
$root . '/infbyte',
Expand Down
27 changes: 27 additions & 0 deletions tests/Feature/SkeletonDistributionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,33 @@

expect($installExitCode)->toBe(0)
->and(hash_file('sha256', $project . '/.env'))->toBe($before);

$deployOutput = [];
$deployExitCode = 0;
exec(sprintf(
'cd %s && bash ./deploy.sh 2>&1',
escapeshellarg($project),
), $deployOutput, $deployExitCode);

expect($deployExitCode)->toBe(0, implode("\n", $deployOutput))
->and($project . '/bootstrap/cache/config/__manifest.php')->toBeFile()
->and($project . '/bootstrap/cache/routes/fused.php')->toBeFile()
->and($project . '/bootstrap/cache/console/commands.php')->toBeFile()
->and($project . '/bootstrap/cache/modules.php')->toBeFile();

$clearOutput = [];
$clearExitCode = 0;
exec(sprintf(
'cd %s && %s infbyte optimize:clear 2>&1',
escapeshellarg($project),
escapeshellarg(PHP_BINARY),
), $clearOutput, $clearExitCode);

expect($clearExitCode)->toBe(0, implode("\n", $clearOutput))
->and($project . '/bootstrap/cache/config/__manifest.php')->not->toBeFile()
->and($project . '/bootstrap/cache/routes/fused.php')->not->toBeFile()
->and($project . '/bootstrap/cache/console/commands.php')->not->toBeFile()
->and($project . '/bootstrap/cache/modules.php')->not->toBeFile();
} finally {
removeInfbyteDistributionFixture($fixture);
}
Expand Down
Loading