PHP ecosystem has changed a lot, far beyond not structural procedural era. With the release of PHP 8.x, this language has become OOP language in enterprise level that very strict, fast, and elegant.
This article will summarize best practices for PHP modern in 4 chapter and is wrote special for facilitating large scale software development that secure, high performance, and maintainable.
Foundation & Code Architecture
This section are how we build the foundation of application. The good code starts from solid architectural design, neat folder structure, and strict type system implementation.
Blueprint 1: Separation of responsibility layers (Layered Architecture) from Controller, Service, to Repository.
Environment & Modern Version
- Use new stable version of PHP (minimal PHP 8.1+, ideally 8.2/8.3). You can take advantage of JIT compiler, enum, readonly properties, and fibers.
- Use
vlucas/phpdotenvfor environment configuration. Never hardcode credentials. Store environment settings in a .env file. - Activate
error_reporting(E_ALL)in development, and turn off display_errors in production (log to file, don't display to user).
Wrting Standart & Type System
- Follow PSR (PHP Standards Recommendations): Use PSR-1 (Basic), PSR-12 (Coding Style), PSR-4 (Autoloading), and PSR-7 (HTTP Message).
- Use
declare(strict_types=1);in first line of file for strict type data. - Absolute Type hinting in all parameter and return type.
public function calculateScore(int $score, string $comment): array - Use Enums & Readonly (PHP 8.1+): Use
readonly propertiesfor immutable data andenumfor fixed value representation instead of class constant.enum SurveyType: string { case NPS = 'nps'; case CSAT = 'csat'; case CES = 'ces'; }
OOP Modeling & System Design
- SOLID Principles: Focus on Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion.
- Layered Architecture & Repository Pattern: Separate Controller, Service, Repository, and Model. Controller must not contain business logic (Service Layer). Abstract data access with Repository.
- Dependency Injection: Don't create objects (
new Object()) hardcode in class. Always inject dependencies via constructor.class SurveyService { public function __construct( private readonly SurveyRepositoryInterface $repository ) {} }
Data Management & Security
Performance and security can not added later, both must be main consideration (by design) since create database architecture until endpoint. Here some best practice that need to applied in data management and security:
Blueprint 2: Optimize cycle data using OPcache and Redis Cache Layer.
Optimal Database Access
- Absolute Prepared Statements: Prevent SQL Injection by always using parameter binding, avoid manual string query concatenation.
$stmt = $pdo->prepare('SELECT * FROM surveys WHERE id = :id'); $stmt->execute(['id' => $id]); - Avoid N+1 Query Problem: When using ORM (such as Eloquent or Doctrine), use eager loading (such as
with()) when fetching relationship data. - Use Database Transactions: Ensure multi-step operations that are related to each other are wrapped in a transaction block so that data consistency is maintained.
Performa Caching & Asynchronous
- Activate OPCache in Production: Very crucial. Save PHP bytecode in memory to avoid re-parsing on every request.
- Caching Layer Implementation: Use Redis or Memcached for the data often accessed but rarely changed.
- Message Queue: Use Message Broker for slow operations (sending email, PDF report generation) so it doesn't block HTTP user response.
Application Security Standart
- Filter In, Escape Out: Validate all user input and clean (escape) output according to context before rendering (e.g. with
htmlspecialchars()for HTML). - Credential Management: Hash your secret value (password, API key) with bcrypt or argon2 algorithm using
password_hash()function. - Rate Limiting & Security Header: Protect public / sensitive endpoint with request boundary, for to HTTPS connection, and set header such as CSP and HSTS.
Reliability & Testing
The reliable system is never fail without user known (silent failure) and has safety net before code goes to production server. Here are best practice that need to be implemented:
Blueprint 3: Visualization of code testing integration and centralized log recording.
Exception Handling
- Forbidden Return Boolean/Null for Error: Use Exception (Throw) instead of throwing
falseif function fails to complete essential task. - Custom Exceptions: Create domain-specific error class so it easy to catch (example:
SurveyNotFoundException). - Global Error Handler: Register top-level exception handler with Sentry or Bugsnag to track stack trace in production. Don't log sensitive data (password, PII) in log file!
TDD & Automatic Test
- Unit Test (PHPUnit): Test business logic in isolation. Use mocking technique to simulate database connection or external API.
- Pola Arrange-Act-Assert: Use this pattern so that test case is structured clearly and explicitly.
public function test_nps_score_is_calculated_correctly(): void { // Arrange $responses = [9, 10, 6, 8]; // Act $result = $this->npsCalculator->calculate($responses); // Assert $this->assertEquals(25, $result); }
Ecosystem & Lifecycle
Developing a modern ecosystem also means involving industry-standart tools, start from third-party dependencies to release strategy. Here are best practice that need to be implemented:
Blueprint 4: The flow of continuous delivery (CI/CD) from local repository to production environment.
Dependency Management (Composer)
- Commit
composer.lockfile: This file ensures that all development team (including CI server) installs the exact same version of third-party libraries. - Separate Dependencies: Use
--devwhen installing testing tools so that the size of vendor in production stays lean and safe from debugging feature exploitation. - Routine Audit: Get used to executing the
composer auditcommand to verify the existence of published vulnerabilities.
Modern API Design
- Consistent Response Format: All API outputs (especially JSON) must have a standard wrapper schema for both success and failure cases.
{ "success": true, "data": {...}, "message": null } - Versioning: Add version parameter to URI (
/api/v1/...) since day one for ensuring backward compatibility in the future.
Deployment & CI/CD Pipeline
- Automated Pipeline: Configure Bitbucket Pipelines, GitHub Actions, or GitLab CI to execute test suite automatically before build.
- Zero-Downtime Deployment: Transition to new version release using symlink (such as Envoyer pattern) or Blue-Green Deployment to avoid network traffic rejection when updates are in progress.
We not only write PHP code that works, but also create a system that is resistant to time (future-proof) and can be read by our fellow engineers.