High-Performance PHP: Optimization Strategies

Proven techniques for optimizing PHP applications to handle high-turnover, high-complexity scenarios.

PHP has a reputation for being slow, but that's largely outdated. Modern PHP 8.2+ with proper optimization can handle thousands of requests per second. The key is knowing where to optimize and how to measure the impact of your changes.

Over the years, I've optimized PHP applications handling millions of requests daily. Here are the techniques that deliver real performance gains.

Performance Measurement Foundation

Profiling Tools

You can't optimize what you don't measure. Essential profiling tools:

# Install Xdebug for profiling
pecl install xdebug
# php.ini configuration
zend_extension=xdebug.so
xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.output_dir="/tmp/xdebug"
xdebug.profiler_output_name="cachegrind.out.%p"

Use with tools like KCacheGrind or Webgrind to visualize performance bottlenecks.

Application Performance Monitoring

<?php
declare(strict_types=1);
namespace AppMonitoring;
use AppValueObjects{MetricName, Duration};
use AppExceptionsTimerNotFoundException;
use PsrLogLoggerInterface;
final class PerformanceMonitor
{
/** @var array<string, float> */
private array $timers = [];
public function __construct(
private readonly LoggerInterface $logger,
private readonly MetricsCollector $metricsCollector,
) {}
public function start(MetricName $name): void
{
$this->timers[$name->value] = hrtime(true);
}
public function end(MetricName $name): Duration
{
$timerKey = $name->value;
if (!isset($this->timers[$timerKey])) {
throw new TimerNotFoundException("Timer '{$timerKey}' not found");
}
$elapsed = Duration::fromNanoseconds(
hrtime(true) - $this->timers[$timerKey]
);
unset($this->timers[$timerKey]);
$this->metricsCollector->timing($name, $elapsed);
$this->logger->debug('Performance metric recorded', [
'metric' => $name->value,
'duration_ms' => $elapsed->toMilliseconds(),
]);
return $elapsed;
}
}

OPcache Optimization

OPcache is the most important PHP optimization. It caches compiled bytecode, eliminating the need to parse and compile PHP files on every request.

Production OPcache Configuration

# php.ini
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=512
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=32531
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.enable_file_override=1
opcache.optimization_level=0x7FFEBFFF
opcache.preload=/var/www/html/preload.php
opcache.preload_user=www-data

OPcache Monitoring

<?php
declare(strict_types=1);
namespace AppMonitoringOPcache;
use AppValueObjects{HitRate, MemoryUsage};
use AppExceptionsOPcacheNotAvailableException;
final readonly class OPcacheMonitor
{
public function __construct(
private OPcacheStatusReader $statusReader,
private OPcacheConfigReader $configReader,
) {}
public function getStats(): OPcacheStats
{
if (!extension_loaded('opcache')) {
throw new OPcacheNotAvailableException('OPcache extension not loaded');
}
$status = $this->statusReader->read();
$config = $this->configReader->read();
return new OPcacheStats(
enabled: $status['opcache_enabled'],
hitRate: HitRate::fromFloat($status['opcache_statistics']['opcache_hit_rate']),
memoryUsage: MemoryUsage::fromArray($status['memory_usage']),
cachedScripts: $status['opcache_statistics']['num_cached_scripts'],
maxCachedKeys: $config['directives']['opcache.max_accelerated_files'],
jitEnabled: $config['directives']['opcache.jit_buffer_size'] > 0,
jitBufferSize: $config['directives']['opcache.jit_buffer_size'],
);
}
public function reset(): void
{
if (!opcache_reset()) {
throw new OPcacheResetFailedException('Failed to reset OPcache');
}
}
public function invalidateFile(string $filePath): void
{
if (!opcache_invalidate($filePath, true)) {
throw new OPcacheInvalidationFailedException(
"Failed to invalidate file: {$filePath}"
);
}
}
}

Database Optimization

Connection Pooling

Database connections are expensive. Use persistent connections wisely:

<?php
declare(strict_types=1);
namespace AppDatabaseConnection;
use AppValueObjects{ConnectionString, ConnectionId};
use AppExceptions{ConnectionPoolExhaustedException, ConnectionCreationFailedException};
use WeakMap;
final class DatabaseConnectionPool
{
/** @var WeakMap<ConnectionId, PDO> */
private WeakMap $connections;
/** @var array<string, ConnectionId> */
private array $connectionIds = [];
public function __construct(
private readonly ConnectionString $dsn,
private readonly DatabaseCredentials $credentials,
private readonly int $maxConnections = 20,
private readonly ConnectionOptions $options = new ConnectionOptions(),
) {
$this->connections = new WeakMap();
}
public function getConnection(): PDO
{
$connectionId = $this->findAvailableConnection()
?? $this->createNewConnection();
return $this->connections[$connectionId];
}
private function findAvailableConnection(): ?ConnectionId
{
foreach ($this->connectionIds as $id) {
if ($this->connections->offsetExists($id)) {
return $id;
}
}
return null;
}
private function createNewConnection(): ConnectionId
{
if (count($this->connectionIds) >= $this->maxConnections) {
throw new ConnectionPoolExhaustedException(
"Maximum connections ({$this->maxConnections}) reached"
);
}
$connectionId = ConnectionId::generate();
try {
$pdo = new PDO(
$this->dsn->value,
$this->credentials->username,
$this->credentials->password,
$this->options->toPdoOptions(),
);
$this->connections[$connectionId] = $pdo;
$this->connectionIds[] = $connectionId;
return $connectionId;
} catch (PDOException $e) {
throw new ConnectionCreationFailedException(
"Failed to create database connection: {$e->getMessage()}",
previous: $e
);
}
}
}

Query Optimization

<?php
declare(strict_types=1);
namespace AppDatabasePerformance;
use AppValueObjects{QueryDuration, QueryMetrics};
use AppExceptionsSlowQueryThresholdExceededException;
use PsrLogLoggerInterface;
final readonly class QueryOptimizer
{
/** @var array<int, QueryMetrics> */
private array $queryLog = [];
public function __construct(
private PDO $pdo,
private LoggerInterface $logger,
private float $slowQueryThreshold = 0.1,
private int $maxSlowQueries = 10,
) {}
public function executeQuery(string $sql, array $params = []): array
{
$startTime = hrtime(true);
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetchAll();
$duration = QueryDuration::fromNanoseconds(hrtime(true) - $startTime);
if ($duration->exceeds($this->slowQueryThreshold)) {
$this->logSlowQuery($sql, $params, $duration);
}
return $result;
}
private function logSlowQuery(string $sql, array $params, QueryDuration $duration): void
{
$metrics = new QueryMetrics(
sql: $sql,
parameters: $params,
duration: $duration,
executedAt: new DateTimeImmutable()
);
$this->queryLog[] = $metrics;
$this->logger->warning('Slow query detected', [
'sql' => $sql,
'duration_ms' => $duration->toMilliseconds(),
'params' => $params,
]);
if (count($this->queryLog) >= $this->maxSlowQueries) {
throw new SlowQueryThresholdExceededException(
"Too many slow queries detected: " . count($this->queryLog)
);
}
}
public function getSlowQueries(): array
{
return $this->queryLog;
}
}

Caching Strategies

Multi-Level Caching

// Code snippet not found: multilevel-cache-manager.php

Smart Cache Invalidation

// Code snippet not found: tagged-cache-invalidator.php

Memory Management

Object Pooling

// Code snippet not found: object-pool.php
// Code snippet not found: http-client-factory.php

Memory Leak Detection

// Code snippet not found: memory-profiler.php

Asynchronous Processing

Job Queue Implementation

// Code snippet not found: redis-job-queue.php
// Code snippet not found: abstract-job.php

HTTP Performance Optimization

Response Streaming

// Code snippet not found: streaming-response.php

Response Compression

// Code snippet not found: compression-middleware.php

Code-Level Optimizations

Efficient Array Operations

// Code snippet not found: array-optimizer.php

String Optimization

// Code snippet not found: string-optimizer.php

Load Testing and Benchmarking

Simple Benchmarking

// Code snippet not found: benchmark.php

Production Monitoring

Real-time Performance Dashboard

// Code snippet not found: performance-dashboard.php

Best Practices Summary

  • Measure first: Use profiling tools to identify bottlenecks
  • Optimize OPcache: Enable and configure properly
  • Cache everything: Use multi-level caching strategies
  • Database optimization: Connection pooling, query optimization
  • Async processing: Move heavy operations to background jobs
  • Memory management: Monitor memory usage and prevent leaks
  • HTTP optimization: Compression, streaming, efficient headers
  • Code optimization: Efficient algorithms and data structures

Common Pitfalls

  • Premature optimization: Profile before optimizing
  • Over-caching: Cache invalidation complexity
  • Ignoring memory limits: Monitor memory usage
  • Database over-optimization: Sometimes simple queries are better
  • Micro-optimizations: Focus on significant bottlenecks

High-performance PHP is achievable with the right techniques and tools. Start with measuring your current performance, identify the biggest bottlenecks, and apply optimizations systematically. Remember: the best optimization is the one that makes a measurable difference in your specific use case.