Azwar's Blog

Modern PHP Development: Best Practices and Advanced Techniques

December 5, 2024 (7mo ago)PHP

Hi everyone! I'm Azwar, a WordPress and PHP developer with three years of experience. In this comprehensive guide, I'll share modern PHP development practices, from object-oriented programming to design patterns and performance optimization. Whether you're working with WordPress, Laravel, or custom PHP applications, these techniques will help you write better code.

Modern PHP Development: Best Practices and Advanced Techniques
Modern PHP Development: Best Practices and Advanced Techniques

PHP has evolved significantly over the years, and modern PHP development requires understanding of object-oriented programming, design patterns, and performance optimization. This guide will help you write clean, maintainable, and efficient PHP code.

Understanding Modern PHP Features

PHP 8.x introduced many powerful features that make development more efficient and code more readable:

Type Declarations and Return Types

<?php declare(strict_types=1); class UserService { private UserRepository $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function createUser(string $name, string $email, int $age): User { if (empty($name) || empty($email)) { throw new InvalidArgumentException('Name and email are required'); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format'); } $user = new User($name, $email, $age); return $this->userRepository->save($user); } public function findUserById(int $id): ?User { return $this->userRepository->findById($id); } public function updateUser(int $id, array $data): User { $user = $this->findUserById($id); if (!$user) { throw new UserNotFoundException("User with ID {$id} not found"); } $user->update($data); return $this->userRepository->save($user); } }

Constructor Property Promotion

PHP 8.0 introduced constructor property promotion, making classes more concise:

<?php class User { public function __construct( private string $name, private string $email, private int $age, private DateTimeImmutable $createdAt = new DateTimeImmutable() ) {} public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; } public function getAge(): int { return $this->age; } public function getCreatedAt(): DateTimeImmutable { return $this->createdAt; } public function update(array $data): void { if (isset($data['name'])) { $this->name = $data['name']; } if (isset($data['email'])) { if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format'); } $this->email = $data['email']; } if (isset($data['age'])) { $this->age = $data['age']; } } }

Implementing Design Patterns

Design patterns help create maintainable and scalable code. Here are some commonly used patterns:

Repository Pattern

The Repository pattern abstracts data access logic:

<?php interface UserRepositoryInterface { public function findById(int $id): ?User; public function findByEmail(string $email): ?User; public function save(User $user): User; public function delete(int $id): bool; public function findAll(): array; } class UserRepository implements UserRepositoryInterface { private PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function findById(int $id): ?User { $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([$id]); $data = $stmt->fetch(PDO::FETCH_ASSOC); if (!$data) { return null; } return $this->createUserFromData($data); } public function findByEmail(string $email): ?User { $stmt = $this->pdo->prepare('SELECT * FROM users WHERE email = ?'); $stmt->execute([$email]); $data = $stmt->fetch(PDO::FETCH_ASSOC); if (!$data) { return null; } return $this->createUserFromData($data); } public function save(User $user): User { if ($user->getId()) { return $this->update($user); } return $this->insert($user); } private function insert(User $user): User { $stmt = $this->pdo->prepare(' INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, ?) '); $stmt->execute([ $user->getName(), $user->getEmail(), $user->getAge(), $user->getCreatedAt()->format('Y-m-d H:i:s') ]); $user->setId((int) $this->pdo->lastInsertId()); return $user; } private function update(User $user): User { $stmt = $this->pdo->prepare(' UPDATE users SET name = ?, email = ?, age = ? WHERE id = ? '); $stmt->execute([ $user->getName(), $user->getEmail(), $user->getAge(), $user->getId() ]); return $user; } public function delete(int $id): bool { $stmt = $this->pdo->prepare('DELETE FROM users WHERE id = ?'); return $stmt->execute([$id]); } public function findAll(): array { $stmt = $this->pdo->query('SELECT * FROM users ORDER BY created_at DESC'); $data = $stmt->fetchAll(PDO::FETCH_ASSOC); return array_map([$this, 'createUserFromData'], $data); } private function createUserFromData(array $data): User { $user = new User( $data['name'], $data['email'], (int) $data['age'], new DateTimeImmutable($data['created_at']) ); $user->setId((int) $data['id']); return $user; } }

Factory Pattern

The Factory pattern helps create objects without specifying their exact classes:

<?php interface LoggerInterface { public function log(string $message, string $level = 'info'): void; } class FileLogger implements LoggerInterface { private string $logFile; public function __construct(string $logFile) { $this->logFile = $logFile; } public function log(string $message, string $level = 'info'): void { $timestamp = date('Y-m-d H:i:s'); $logEntry = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL; file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX); } } class DatabaseLogger implements LoggerInterface { private PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function log(string $message, string $level = 'info'): void { $stmt = $this->pdo->prepare(' INSERT INTO logs (message, level, created_at) VALUES (?, ?, ?) '); $stmt->execute([$message, $level, date('Y-m-d H:i:s')]); } } class LoggerFactory { public static function create(string $type, array $config = []): LoggerInterface { return match ($type) { 'file' => new FileLogger($config['log_file'] ?? 'app.log'), 'database' => new DatabaseLogger($config['pdo']), default => throw new InvalidArgumentException("Unknown logger type: {$type}") }; } } // Usage $logger = LoggerFactory::create('file', ['log_file' => 'custom.log']); $logger->log('Application started', 'info');

Observer Pattern

The Observer pattern allows objects to subscribe to events:

<?php interface ObserverInterface { public function update(string $event, array $data = []): void; } interface SubjectInterface { public function attach(ObserverInterface $observer): void; public function detach(ObserverInterface $observer): void; public function notify(string $event, array $data = []): void; } class UserManager implements SubjectInterface { private array $observers = []; public function attach(ObserverInterface $observer): void { $this->observers[] = $observer; } public function detach(ObserverInterface $observer): void { $key = array_search($observer, $this->observers, true); if ($key !== false) { unset($this->observers[$key]); } } public function notify(string $event, array $data = []): void { foreach ($this->observers as $observer) { $observer->update($event, $data); } } public function createUser(string $name, string $email): User { $user = new User($name, $email, 25); // Notify observers about user creation $this->notify('user.created', ['user' => $user]); return $user; } public function deleteUser(int $id): bool { // Delete user logic here // Notify observers about user deletion $this->notify('user.deleted', ['user_id' => $id]); return true; } } class EmailNotifier implements ObserverInterface { public function update(string $event, array $data = []): void { match ($event) { 'user.created' => $this->sendWelcomeEmail($data['user']), 'user.deleted' => $this->sendGoodbyeEmail($data['user_id']), default => null }; } private function sendWelcomeEmail(User $user): void { // Send welcome email logic echo "Welcome email sent to {$user->getEmail()}\n"; } private function sendGoodbyeEmail(int $userId): void { // Send goodbye email logic echo "Goodbye email sent for user ID {$userId}\n"; } } class ActivityLogger implements ObserverInterface { public function update(string $event, array $data = []): void { $timestamp = date('Y-m-d H:i:s'); $logMessage = "[{$timestamp}] Event: {$event}"; if (!empty($data)) { $logMessage .= " Data: " . json_encode($data); } file_put_contents('activity.log', $logMessage . PHP_EOL, FILE_APPEND); } }

Error Handling and Exceptions

Modern PHP applications should use proper exception handling:

<?php class UserNotFoundException extends Exception { public function __construct(int $userId) { parent::__construct("User with ID {$userId} not found"); } } class InvalidUserDataException extends Exception { public function __construct(string $field, string $message) { parent::__construct("Invalid {$field}: {$message}"); } } class UserService { public function __construct( private UserRepository $userRepository, private LoggerInterface $logger ) {} public function createUser(array $data): User { try { $this->validateUserData($data); $user = new User( $data['name'], $data['email'], $data['age'] ?? 25 ); $savedUser = $this->userRepository->save($user); $this->logger->log("User created: {$savedUser->getEmail()}", 'info'); return $savedUser; } catch (InvalidUserDataException $e) { $this->logger->log("User creation failed: {$e->getMessage()}", 'error'); throw $e; } catch (Exception $e) { $this->logger->log("Unexpected error: {$e->getMessage()}", 'error'); throw new RuntimeException('Failed to create user', 0, $e); } } private function validateUserData(array $data): void { if (empty($data['name'])) { throw new InvalidUserDataException('name', 'Name is required'); } if (empty($data['email'])) { throw new InvalidUserDataException('email', 'Email is required'); } if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new InvalidUserDataException('email', 'Invalid email format'); } if (isset($data['age']) && ($data['age'] < 0 || $data['age'] > 150)) { throw new InvalidUserDataException('age', 'Age must be between 0 and 150'); } } }

Performance Optimization

Optimize your PHP applications for better performance:

Database Optimization

<?php class OptimizedUserRepository implements UserRepositoryInterface { private PDO $pdo; private array $cache = []; public function __construct(PDO $pdo) { $this->pdo = $pdo; $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); } public function findById(int $id): ?User { $cacheKey = "user_{$id}"; if (isset($this->cache[$cacheKey])) { return $this->cache[$cacheKey]; } $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([$id]); $data = $stmt->fetch(); if (!$data) { return null; } $user = $this->createUserFromData($data); $this->cache[$cacheKey] = $user; return $user; } public function findByIds(array $ids): array { if (empty($ids)) { return []; } $placeholders = str_repeat('?,', count($ids) - 1) . '?'; $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id IN ({$placeholders})"); $stmt->execute($ids); $users = []; while ($data = $stmt->fetch()) { $user = $this->createUserFromData($data); $users[$user->getId()] = $user; $this->cache["user_{$user->getId()}"] = $user; } return $users; } }

Memory Management

<?php class MemoryEfficientProcessor { public function processLargeDataset(string $filename): void { $handle = fopen($filename, 'r'); if (!$handle) { throw new RuntimeException("Cannot open file: {$filename}"); } try { while (($line = fgets($handle)) !== false) { $this->processLine(trim($line)); // Clear memory periodically if (memory_get_usage() > 50 * 1024 * 1024) { // 50MB gc_collect_cycles(); } } } finally { fclose($handle); } } private function processLine(string $line): void { // Process individual line $data = json_decode($line, true); if ($data) { // Process data $this->saveData($data); } } private function saveData(array $data): void { // Save data to database or file // Implementation depends on your needs } }

Testing Your PHP Code

Write tests to ensure your code works correctly:

<?php use PHPUnit\Framework\TestCase; class UserServiceTest extends TestCase { private UserService $userService; private UserRepository $userRepository; private LoggerInterface $logger; protected function setUp(): void { $this->userRepository = $this->createMock(UserRepository::class); $this->logger = $this->createMock(LoggerInterface::class); $this->userService = new UserService($this->userRepository, $this->logger); } public function testCreateUserWithValidData(): void { $userData = [ 'name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30 ]; $expectedUser = new User('John Doe', 'john@example.com', 30); $this->userRepository ->expects($this->once()) ->method('save') ->with($this->equalTo($expectedUser)) ->willReturn($expectedUser); $this->logger ->expects($this->once()) ->method('log') ->with($this->stringContains('User created: john@example.com'), 'info'); $result = $this->userService->createUser($userData); $this->assertEquals($expectedUser, $result); } public function testCreateUserWithInvalidEmail(): void { $userData = [ 'name' => 'John Doe', 'email' => 'invalid-email' ]; $this->expectException(InvalidUserDataException::class); $this->expectExceptionMessage('Invalid email: Invalid email format'); $this->userService->createUser($userData); } public function testCreateUserWithMissingName(): void { $userData = [ 'email' => 'john@example.com' ]; $this->expectException(InvalidUserDataException::class); $this->expectExceptionMessage('Invalid name: Name is required'); $this->userService->createUser($userData); } }

Best Practices for Modern PHP Development

  1. Use Type Declarations: Always use type hints and return types
  2. Follow PSR Standards: Adhere to PSR-12 coding standards
  3. Implement Design Patterns: Use appropriate patterns for your use cases
  4. Write Tests: Ensure your code is thoroughly tested
  5. Handle Errors Properly: Use exceptions for error handling
  6. Optimize Performance: Monitor and optimize database queries and memory usage
  7. Use Dependency Injection: Make your code more testable and maintainable
  8. Document Your Code: Write clear documentation and comments

Conclusion

Modern PHP development requires understanding of object-oriented programming, design patterns, and performance optimization. By following these practices and techniques, you'll write cleaner, more maintainable, and efficient PHP code.

Remember to always prioritize code quality, security, and performance in your PHP applications.

Additional Resources

Comments