feat: implement OPcache preload script for improved performance

This commit is contained in:
2025-12-21 07:23:17 -05:00
parent 261700eabb
commit a5f3c904f6

View File

@@ -1,103 +1,297 @@
<?php
require __DIR__ . '/vendor/autoload.php';
$app = require __DIR__ . '/bootstrap/app.php';
/**
* OPcache Preload Script for PHP 8.3-FPM
*
* Configure in php.ini:
* opcache.preload=/path/to/preload.php
* opcache.preload_user=www-data
*
* Run after deployment: php artisan optimize && php artisan filament:optimize
*/
$classesToPreload = [
// Core/HTTP
\Illuminate\Http\Request::class,
\Illuminate\Http\Response::class,
\Illuminate\Routing\Router::class,
\Illuminate\Foundation\Application::class,
declare(strict_types=1);
// Database/Eloquent
\Illuminate\Database\Eloquent\Model::class,
\Illuminate\Database\Eloquent\Builder::class,
\Illuminate\Database\Query\Builder::class,
\Illuminate\Database\Eloquent\Relations\Relation::class,
\Illuminate\Database\Eloquent\Collection::class,
$basePath = __DIR__;
$preloadedCount = 0;
$skippedCount = 0;
// Container/Support
\Illuminate\Container\Container::class,
\Illuminate\Support\Collection::class,
\Illuminate\Support\Str::class,
\Illuminate\Support\Arr::class,
\Illuminate\Support\Facades\Facade::class,
// Common middleware
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// Auth related
\Illuminate\Auth\AuthManager::class,
\Illuminate\Auth\SessionGuard::class,
\Illuminate\Auth\EloquentUserProvider::class,
// Queue related
\Illuminate\Queue\Worker::class,
\Illuminate\Queue\QueueManager::class,
\Illuminate\Queue\Jobs\Job::class,
];
function preloadClass($class) {
if (class_exists($class, false)) {
// Already loaded
return;
/**
* Compile a file into OPcache without executing it
*/
function preloadFile(string $path): bool
{
global $preloadedCount, $skippedCount;
if (!is_file($path) || !is_readable($path)) {
return false;
}
if (!class_exists($class)) {
return;
}
$rc = new ReflectionClass($class);
// Preload methods and their static variables
foreach ($rc->getMethods() as $method) {
if ($method->isPublic() && !$method->isAbstract()) {
$method->getStaticVariables();
try {
if (opcache_compile_file($path)) {
$preloadedCount++;
return true;
}
} catch (Throwable $e) {
// Silently skip files that can't be preloaded (syntax errors, etc.)
}
// Preload parent classes
$parent = $rc->getParentClass();
if ($parent) {
preloadClass($parent->getName());
}
// Preload interfaces
foreach ($rc->getInterfaces() as $interface) {
preloadClass($interface->getName());
}
$skippedCount++;
return false;
}
function preloadDirectory($dir, $pattern) {
/**
* Recursively preload all PHP files in a directory
*/
function preloadDirectory(string $dir, array $excludePatterns = []): void
{
if (!is_dir($dir)) {
return;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if ($file->isFile() && preg_match($pattern, $file->getPathname())) {
require_once $file->getPathname();
if (!$file->isFile() || $file->getExtension() !== 'php') {
continue;
}
$path = $file->getRealPath();
// Skip excluded patterns
foreach ($excludePatterns as $pattern) {
if (preg_match($pattern, $path)) {
continue 2;
}
}
preloadFile($path);
}
}
/**
* Preload specific files from a list
*/
function preloadFiles(array $files): void
{
foreach ($files as $file) {
if (is_file($file)) {
preloadFile($file);
}
}
}
// Preload framework classes
foreach ($classesToPreload as $class) {
preloadClass($class);
// =============================================================================
// 1. LARAVEL CACHED/COMPILED FILES (Most Critical)
// =============================================================================
$cachedFiles = [
$basePath . '/bootstrap/cache/packages.php',
$basePath . '/bootstrap/cache/services.php',
$basePath . '/bootstrap/cache/config.php',
$basePath . '/bootstrap/cache/routes-v7.php',
$basePath . '/bootstrap/cache/events.php',
$basePath . '/bootstrap/cache/filament.php',
$basePath . '/bootstrap/cache/livewire-components.php',
];
preloadFiles($cachedFiles);
// =============================================================================
// 2. COMPOSER AUTOLOADER (Essential for class resolution)
// =============================================================================
preloadFiles([
$basePath . '/vendor/autoload.php',
$basePath . '/vendor/composer/autoload_real.php',
$basePath . '/vendor/composer/ClassLoader.php',
$basePath . '/vendor/composer/autoload_static.php',
]);
// =============================================================================
// 3. LARAVEL CORE FRAMEWORK
// =============================================================================
$laravelCoreDirs = [
// Foundation & Core
$basePath . '/vendor/laravel/framework/src/Illuminate/Foundation',
$basePath . '/vendor/laravel/framework/src/Illuminate/Container',
$basePath . '/vendor/laravel/framework/src/Illuminate/Contracts',
// HTTP Layer
$basePath . '/vendor/laravel/framework/src/Illuminate/Http',
$basePath . '/vendor/laravel/framework/src/Illuminate/Routing',
$basePath . '/vendor/laravel/framework/src/Illuminate/Pipeline',
// Database & Eloquent
$basePath . '/vendor/laravel/framework/src/Illuminate/Database',
// Support utilities
$basePath . '/vendor/laravel/framework/src/Illuminate/Support',
$basePath . '/vendor/laravel/framework/src/Illuminate/Collections',
// Session & Auth
$basePath . '/vendor/laravel/framework/src/Illuminate/Auth',
$basePath . '/vendor/laravel/framework/src/Illuminate/Session',
$basePath . '/vendor/laravel/framework/src/Illuminate/Cookie',
// Views & Blade
$basePath . '/vendor/laravel/framework/src/Illuminate/View',
// Events & Queue
$basePath . '/vendor/laravel/framework/src/Illuminate/Events',
$basePath . '/vendor/laravel/framework/src/Illuminate/Queue',
// Validation & Cache
$basePath . '/vendor/laravel/framework/src/Illuminate/Validation',
$basePath . '/vendor/laravel/framework/src/Illuminate/Cache',
// Encryption & Hashing
$basePath . '/vendor/laravel/framework/src/Illuminate/Encryption',
$basePath . '/vendor/laravel/framework/src/Illuminate/Hashing',
];
// Exclude test files and stubs
$excludePatterns = [
'/[Tt]est[s]?\.php$/',
'/[Ss]tub[s]?\.php$/',
'/[Ff]ixture[s]?/',
];
foreach ($laravelCoreDirs as $dir) {
preloadDirectory($dir, $excludePatterns);
}
// Preload application directories
preloadDirectory(__DIR__ . '/app/Http/Controllers', '/\.php$/');
preloadDirectory(__DIR__ . '/app/Models', '/\.php$/');
preloadDirectory(__DIR__ . '/app/Jobs', '/\.php$/');
preloadDirectory(__DIR__ . '/app/Providers', '/\.php$/');
// =============================================================================
// 4. LIVEWIRE (Critical for your app)
// =============================================================================
// Free memory
gc_collect_cycles();
preloadDirectory($basePath . '/vendor/livewire/livewire/src', $excludePatterns);
preloadDirectory($basePath . '/vendor/livewire/volt/src', $excludePatterns);
// =============================================================================
// 5. FILAMENT (Admin Panel - Critical for your app)
// =============================================================================
$filamentDirs = [
$basePath . '/vendor/filament/filament/src',
$basePath . '/vendor/filament/support/src',
$basePath . '/vendor/filament/forms/src',
$basePath . '/vendor/filament/tables/src',
$basePath . '/vendor/filament/actions/src',
$basePath . '/vendor/filament/infolists/src',
$basePath . '/vendor/filament/notifications/src',
$basePath . '/vendor/filament/widgets/src',
];
foreach ($filamentDirs as $dir) {
preloadDirectory($dir, $excludePatterns);
}
// =============================================================================
// 6. COMMON DEPENDENCIES
// =============================================================================
$commonDeps = [
// Symfony HTTP Foundation
$basePath . '/vendor/symfony/http-foundation/Request.php',
$basePath . '/vendor/symfony/http-foundation/Response.php',
$basePath . '/vendor/symfony/http-foundation/HeaderBag.php',
$basePath . '/vendor/symfony/http-foundation/ParameterBag.php',
$basePath . '/vendor/symfony/http-foundation/InputBag.php',
$basePath . '/vendor/symfony/http-foundation/FileBag.php',
$basePath . '/vendor/symfony/http-foundation/ServerBag.php',
$basePath . '/vendor/symfony/http-foundation/Cookie.php',
$basePath . '/vendor/symfony/http-foundation/Session/Session.php',
// Symfony HTTP Kernel
$basePath . '/vendor/symfony/http-kernel/HttpKernel.php',
// PSR Interfaces
$basePath . '/vendor/psr/container/src/ContainerInterface.php',
$basePath . '/vendor/psr/http-message/src/MessageInterface.php',
$basePath . '/vendor/psr/http-message/src/RequestInterface.php',
$basePath . '/vendor/psr/http-message/src/ResponseInterface.php',
$basePath . '/vendor/psr/log/src/LoggerInterface.php',
// Carbon (Date handling)
$basePath . '/vendor/nesbot/carbon/src/Carbon/Carbon.php',
$basePath . '/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php',
$basePath . '/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php',
];
preloadFiles($commonDeps);
// =============================================================================
// 7. APPLICATION CODE (Your code)
// =============================================================================
$appDirs = [
// Models (Most frequently accessed)
$basePath . '/app/Models',
// HTTP Layer
$basePath . '/app/Http/Controllers',
$basePath . '/app/Http/Middleware',
$basePath . '/app/Http/Requests',
// Livewire Components
$basePath . '/app/Livewire',
// Filament Admin
$basePath . '/app/Admin/Resources',
$basePath . '/app/Admin/Pages',
$basePath . '/app/Admin/Widgets',
// Service Providers
$basePath . '/app/Providers',
// Jobs & Events
$basePath . '/app/Jobs',
$basePath . '/app/Events',
$basePath . '/app/Listeners',
// Helpers & Classes
$basePath . '/app/Classes',
$basePath . '/app/Helpers',
$basePath . '/app/Traits',
// Policies & Rules
$basePath . '/app/Policies',
$basePath . '/app/Rules',
];
foreach ($appDirs as $dir) {
preloadDirectory($dir);
}
// =============================================================================
// 8. BLADE COMPILED VIEWS (if using view caching)
// =============================================================================
// Only preload if compiled views exist and are recent
$compiledViewsPath = $basePath . '/storage/framework/views';
if (is_dir($compiledViewsPath)) {
// Limit to most recently modified compiled views (top 200)
$viewFiles = glob($compiledViewsPath . '/*.php');
if ($viewFiles) {
// Sort by modification time, newest first
usort($viewFiles, fn($a, $b) => filemtime($b) - filemtime($a));
$viewFiles = array_slice($viewFiles, 0, 200);
preloadFiles($viewFiles);
}
}
// =============================================================================
// SUMMARY (logged to error_log for debugging)
// =============================================================================
if (php_sapi_name() === 'cli') {
echo "OPcache Preload Complete: {$preloadedCount} files preloaded, {$skippedCount} skipped\n";
echo "Memory usage: " . round(memory_get_usage(true) / 1024 / 1024, 2) . " MB\n";
} else {
error_log("OPcache Preload: {$preloadedCount} files preloaded, {$skippedCount} skipped");
}