feat: implement OPcache preload script for improved performance
This commit is contained in:
348
preload.php
348
preload.php
@@ -1,103 +1,297 @@
|
|||||||
<?php
|
<?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 = [
|
declare(strict_types=1);
|
||||||
// Core/HTTP
|
|
||||||
\Illuminate\Http\Request::class,
|
|
||||||
\Illuminate\Http\Response::class,
|
|
||||||
\Illuminate\Routing\Router::class,
|
|
||||||
\Illuminate\Foundation\Application::class,
|
|
||||||
|
|
||||||
// Database/Eloquent
|
$basePath = __DIR__;
|
||||||
\Illuminate\Database\Eloquent\Model::class,
|
$preloadedCount = 0;
|
||||||
\Illuminate\Database\Eloquent\Builder::class,
|
$skippedCount = 0;
|
||||||
\Illuminate\Database\Query\Builder::class,
|
|
||||||
\Illuminate\Database\Eloquent\Relations\Relation::class,
|
|
||||||
\Illuminate\Database\Eloquent\Collection::class,
|
|
||||||
|
|
||||||
// Container/Support
|
/**
|
||||||
\Illuminate\Container\Container::class,
|
* Compile a file into OPcache without executing it
|
||||||
\Illuminate\Support\Collection::class,
|
*/
|
||||||
\Illuminate\Support\Str::class,
|
function preloadFile(string $path): bool
|
||||||
\Illuminate\Support\Arr::class,
|
{
|
||||||
\Illuminate\Support\Facades\Facade::class,
|
global $preloadedCount, $skippedCount;
|
||||||
|
|
||||||
// Common middleware
|
if (!is_file($path) || !is_readable($path)) {
|
||||||
\Illuminate\Cookie\Middleware\EncryptCookies::class,
|
return false;
|
||||||
\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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!class_exists($class)) {
|
try {
|
||||||
return;
|
if (opcache_compile_file($path)) {
|
||||||
}
|
$preloadedCount++;
|
||||||
|
return true;
|
||||||
$rc = new ReflectionClass($class);
|
|
||||||
// Preload methods and their static variables
|
|
||||||
foreach ($rc->getMethods() as $method) {
|
|
||||||
if ($method->isPublic() && !$method->isAbstract()) {
|
|
||||||
$method->getStaticVariables();
|
|
||||||
}
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
// Silently skip files that can't be preloaded (syntax errors, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload parent classes
|
$skippedCount++;
|
||||||
$parent = $rc->getParentClass();
|
return false;
|
||||||
if ($parent) {
|
|
||||||
preloadClass($parent->getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preload interfaces
|
|
||||||
foreach ($rc->getInterfaces() as $interface) {
|
|
||||||
preloadClass($interface->getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function preloadDirectory($dir, $pattern) {
|
/**
|
||||||
|
* Recursively preload all PHP files in a directory
|
||||||
|
*/
|
||||||
|
function preloadDirectory(string $dir, array $excludePatterns = []): void
|
||||||
|
{
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$iterator = new RecursiveIteratorIterator(
|
$iterator = new RecursiveIteratorIterator(
|
||||||
new RecursiveDirectoryIterator($dir)
|
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($iterator as $file) {
|
foreach ($iterator as $file) {
|
||||||
if ($file->isFile() && preg_match($pattern, $file->getPathname())) {
|
if (!$file->isFile() || $file->getExtension() !== 'php') {
|
||||||
require_once $file->getPathname();
|
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) {
|
// 1. LARAVEL CACHED/COMPILED FILES (Most Critical)
|
||||||
preloadClass($class);
|
// =============================================================================
|
||||||
|
|
||||||
|
$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$/');
|
// 4. LIVEWIRE (Critical for your app)
|
||||||
preloadDirectory(__DIR__ . '/app/Models', '/\.php$/');
|
// =============================================================================
|
||||||
preloadDirectory(__DIR__ . '/app/Jobs', '/\.php$/');
|
|
||||||
preloadDirectory(__DIR__ . '/app/Providers', '/\.php$/');
|
|
||||||
|
|
||||||
// Free memory
|
preloadDirectory($basePath . '/vendor/livewire/livewire/src', $excludePatterns);
|
||||||
gc_collect_cycles();
|
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