feat: implement OPcache preload script for improved performance
This commit is contained in:
348
preload.php
348
preload.php
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user