Introduction to PHP #
No other programming language has a journey as popular yet as controversial as PHP. Starting as a set of Personal Home Page scripts that Rasmus Lerdorf wrote in 1994 to track visitors to his own website, PHP grew into the language powering more than 75% of server-side web on the internet — including WordPress, Wikipedia, and Facebook in its early days. Along the way, PHP was declared “dead” by the developer community several times, but every time, PHP came back with a more modern, faster, and more secure version. PHP 8.x with its JIT compiler, union types, fibers, and named arguments is proof that this language keeps evolving seriously. This article covers where PHP came from, what has kept it alive for three decades, how its ecosystem works, and when PHP is still a sensible choice in 2024.
PHP’s Philosophy and Server-Side Execution Model #
Understanding how PHP fundamentally works is important before discussing its features. PHP is a language embedded directly into HTML and executed on the server — not in the browser.
When a browser requests a PHP page, the server runs the PHP interpreter, executes the code, and sends the rendered HTML to the browser. The browser never sees the PHP code — only its output.
sequenceDiagram
participant Browser
participant WebServer as Web Server (Nginx/Apache)
participant PHPFPM as PHP-FPM
participant DB as Database
Browser->>WebServer: GET /article.php
WebServer->>PHPFPM: Forward request
PHPFPM->>DB: SELECT * FROM articles WHERE id = 1
DB-->>PHPFPM: Row data
PHPFPM->>PHPFPM: Execute PHP, render HTML
PHPFPM-->>WebServer: HTML output
WebServer-->>Browser: HTTP Response (HTML)
Note over Browser: PHP is invisible — only HTMLThis model is fundamentally different from JavaScript (running in the browser) or Go/Java (long-running processes listening for connections). By default, every PHP request is a fresh process starting from zero — there’s no persistent state between requests except what’s explicitly stored in a database, session, or cache.
This philosophy — the share nothing architecture — makes PHP very easy to scale horizontally: just add servers, no state coordination between instances needed.
flowchart TD
A[Request 1] --> B[PHP Process 1\nFresh state]
C[Request 2] --> D[PHP Process 2\nFresh state]
E[Request 3] --> F[PHP Process 3\nFresh state]
B --> G[(Database / Redis / Session)]
D --> G
F --> G
G --> B
G --> D
G --> FHistory and Evolution of PHP #
PHP is one of the most dramatically evolving languages in programming — from a simple CGI script to a modern language with a JIT compiler and a solid type system.
| Year | Version | Major Milestone |
|---|---|---|
| 1994 | PHP/FI | Rasmus Lerdorf writes CGI scripts to track visitors on his personal site |
| 1995 | PHP/FI 2.0 | Released publicly as open source, supports forms and basic databases |
| 1997 | PHP 3.0 | Zeev Suraski & Andi Gutmans rewrite the interpreter; mass adoption begins |
| 2000 | PHP 4.0 | Zend Engine 1.0 — significant performance gains, session support |
| 2004 | PHP 5.0 | Zend Engine 2.0 — full OOP, PDO, Exceptions, SPL |
| 2009 | PHP 5.3 | Namespaces, late static binding, anonymous functions/closures |
| 2013 | PHP 5.5 | Generators, finally blocks, built-in password_hash() |
| 2015 | PHP 7.0 | Zend Engine 3.0 — 2x faster than PHP 5.6, scalar type declarations |
| 2016 | PHP 7.1 | Nullable types, void returns, list() in foreach |
| 2020 | PHP 8.0 | JIT compiler, union types, named arguments, attributes, match expressions |
| 2021 | PHP 8.1 | Fibers, enums, readonly properties, intersection types |
| 2022 | PHP 8.2 | Readonly classes, DNF types, standalone true/false/null types |
| 2023 | PHP 8.3 | Typed class constants, json_validate(), granular DateTime exceptions |
The biggest leap in PHP’s history was the transition from PHP 5 to PHP 7. PHP 7 wasn’t just an incremental update — it was an engine rewrite with a two-fold performance increase and drastic memory usage reduction. Many applications moving from PHP 5.6 to PHP 7.0 immediately experienced significant response time improvements without changing a single line of code.
PHP 8.0 then brought the JIT (Just-In-Time) compiler — something the PHP community had dreamed about for over a decade. Although JIT’s benefits for regular web applications aren’t as dramatic as hoped (since the bottleneck is usually I/O, not CPU), JIT opened the door for PHP into domains that were previously impractical, like numerical computing and image processing.
stateDiagram-v2
[*] --> ScriptingEra: 1994-1999
ScriptingEra --> OOPEra: PHP 5.0 (2004)
OOPEra --> ModernEra: PHP 7.0 (2015)
ModernEra --> TypedEra: PHP 8.0 (2020)
TypedEra --> [*]
ScriptingEra: CGI scripts, HTML embedding, form handling
OOPEra: Mature OOP, namespaces, closures, PDO
ModernEra: 2x faster, scalar types, return types
TypedEra: JIT, union types, enums, fibers, readonlyModern PHP 8.x Features #
PHP 8.x brings features that bring PHP closer to modern languages like Kotlin or Swift in terms of expressiveness and type safety.
Named Arguments #
Named arguments let you pass arguments by parameter name instead of position — making calls to functions with many optional parameters far more readable.
<?php
// ANTI-PATTERN: positional arguments — hard to read, prone to order mistakes
$result = array_slice($array, 0, null, true);
// CORRECT: named arguments — clear without opening the docs
$result = array_slice(array: $array, offset: 0, preserve_keys: true);
// Named arguments are very useful for functions with many boolean flags
// ANTI-PATTERN:
setcookie('user', 'Unis', 0, '', '', true, true);
// CORRECT:
setcookie(
name: 'user',
value: 'Unis',
secure: true,
httponly: true
);
Match Expressions #
match is a safer replacement for switch: strict comparison, no fall-through, and it must be exhaustive (or have a default).
<?php
$status = 404;
// ANTI-PATTERN: switch with loose comparison and easy-to-forget fall-through
switch ($status) {
case 200:
$label = 'OK';
break;
case 404:
$label = 'Not Found';
break;
default:
$label = 'Unknown';
}
// CORRECT: match — strict, no fall-through, returns a value directly
$label = match($status) {
200, 201 => 'Success',
301, 302 => 'Redirect',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown',
};
echo $label; // Not Found
Union Types and Intersection Types #
<?php
// Union types — a parameter can accept more than one type
function processInput(int|string $input): string {
return is_int($input) ? "Number: $input" : "String: $input";
}
echo processInput(42); // Number: 42
echo processInput("hello"); // String: hello
// Nullable shorthand
function findUser(int $id): ?array { // ?array = array|null
// return an array or null if not found
return $id > 0 ? ['id' => $id, 'name' => 'Unis'] : null;
}
// Intersection types (PHP 8.1) — the object must implement ALL interfaces
interface Serializable {}
interface Loggable {}
function saveAndLog(Serializable&Loggable $object): void {
// $object is guaranteed to have all methods from both interfaces
}
Enums (PHP 8.1) #
PHP finally has native enums — not simulated class constants, but a real type that can have methods and implement interfaces.
<?php
// ANTI-PATTERN: class constants — not type-safe, no validation
class OldStatus {
const ACTIVE = 'active';
const INACTIVE = 'inactive';
const PENDING = 'pending';
}
// Problem: functions accept any string, no validation possible
function changeStatus(string $status): void { /* ... */ }
changeStatus('typo'); // accepted without error!
// CORRECT: backed enum — type-safe, integrates with databases
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
case Pending = 'pending';
public function label(): string {
return match($this) {
Status::Active => 'Active',
Status::Inactive => 'Inactive',
Status::Pending => 'Pending Confirmation',
};
}
public function isActive(): bool {
return $this === Status::Active;
}
}
function changeStatus(Status $status): void { /* ... */ }
changeStatus(Status::Active); // correct
// changeStatus('active'); // TypeError — can't send arbitrary strings anymore
echo Status::Active->label(); // Active
echo Status::from('pending')->label(); // Pending Confirmation
Fibers (PHP 8.1) #
Fibers are a cooperative concurrency primitive — similar to coroutines — allowing function execution to be paused and resumed without blocking the whole process. This is the foundation of async PHP at the language level.
<?php
$fiber = new Fiber(function(): string {
$value = Fiber::suspend('fiber started'); // pause, send a value out
echo "Fiber resumed with value: $value\n";
return 'done';
});
$firstValue = $fiber->start(); // start the fiber, get the suspended value
echo "Fiber suspended: $firstValue\n"; // fiber started
$finalResult = $fiber->resume('hello'); // resume the fiber with a value
echo "Fiber returned: $finalResult\n"; // done
Fibers in PHP aren’t async/await like in JavaScript or Dart. PHP has no built-in event loop — Fibers are only a primitive that lets async libraries like ReactPHP or Amp build higher-level abstractions on top. For regular web applications with PHP-FPM, Fibers aren’t directly relevant — what matters is the libraries that use them behind the scenes.
The Ecosystem: Composer and Packagist #
Composer is PHP’s package manager that revolutionized how PHP developers manage dependencies. Before Composer (2012), PHP had no universal library distribution standard — developers used PEAR, manually copied files, or relied on framework-bundled libraries.
# Install dependencies
composer require laravel/framework
composer require guzzlehttp/guzzle # HTTP client
composer require symfony/console # CLI framework
# Dev dependencies
composer require --dev phpunit/phpunit
composer require --dev laravel/pint # code formatter
# Management
composer install # install from composer.lock (production)
composer update # update to the newest compatible versions
composer dump-autoload # regenerate the autoloader
# Information
composer show # list all installed packages
composer outdated # check packages that need updating
An example composer.json for a Laravel application:
{
"name": "my/application",
"description": "A Laravel web application",
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"guzzlehttp/guzzle": "^7.2",
"predis/predis": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"laravel/pint": "^1.0",
"mockery/mockery": "^1.4",
"fakerphp/faker": "^1.9"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
Popular Packages by Category #
| Category | Package | Use |
|---|---|---|
| Web Framework | laravel/framework, symfony | Full-stack web frameworks |
| Micro Framework | slim/slim, silex | Lightweight routing + middleware |
| HTTP Client | guzzlehttp/guzzle, symfony/http-client | Consuming external APIs |
| ORM | illuminate/database, doctrine/orm | Database interaction |
| Testing | phpunit/phpunit, pestphp/pest | Unit and feature testing |
| Templating | twig/twig, blade (Laravel) | Template engines |
| Queue | laravel/horizon, bernard/bernard | Background job processing |
| Linting | squizlabs/php_codesniffer, phpstan | Static analysis and style |
| Auth | firebase/php-jwt, league/oauth2-server | Authentication and authorization |
| CLI | symfony/console | Command-line applications |
PHP Frameworks #
PHP has the most diverse framework ecosystem in web development. Each framework has a different philosophy and trade-offs.
Laravel #
Laravel is the most popular PHP framework today. Its syntax is expressive, its ecosystem is rich, and its documentation is excellent. Laravel suits almost any size of web project.
<?php
// routes/web.php
use App\Http\Controllers\ArticleController;
Route::middleware(['auth'])->group(function () {
Route::get('/articles', [ArticleController::class, 'index']);
Route::post('/articles', [ArticleController::class, 'store']);
Route::put('/articles/{article}', [ArticleController::class, 'update']);
Route::delete('/articles/{article}', [ArticleController::class, 'destroy']);
});
// app/Http/Controllers/ArticleController.php
class ArticleController extends Controller
{
public function index(): View
{
$articles = Article::with('author')
->where('published', true)
->orderByDesc('created_at')
->paginate(15);
return view('articles.index', compact('articles'));
}
public function store(StoreArticleRequest $request): RedirectResponse
{
$article = auth()->user()->articles()->create(
$request->validated()
);
return redirect()->route('articles.show', $article)
->with('success', 'Article published successfully.');
}
}
Symfony #
Symfony is a framework designed for large enterprise projects. More verbose than Laravel, but more flexible and modular — many Symfony components are used by other frameworks, including Laravel.
<?php
// src/Controller/ArticleController.php
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ArticleController extends AbstractController
{
public function __construct(
private readonly ArticleRepository $repository,
private readonly LoggerInterface $logger,
) {}
#[Route('/articles', name: 'article_list', methods: ['GET'])]
public function index(): Response
{
$articles = $this->repository->findPublished();
return $this->render('articles/index.html.twig', [
'articles' => $articles,
]);
}
}
| Criterion | Laravel | Symfony | Slim | CodeIgniter |
|---|---|---|---|---|
| Learning Curve | Medium | High | Low | Low |
| Scalability | ★★★★☆ | ★★★★★ | ★★★☆☆ | ★★★☆☆ |
| Ecosystem | ★★★★★ | ★★★★★ | ★★☆☆☆ | ★★★☆☆ |
| Performance | ★★★★☆ | ★★★★☆ | ★★★★★ | ★★★★☆ |
| Documentation | ★★★★★ | ★★★★☆ | ★★★☆☆ | ★★★★☆ |
| Best for | Startup–Enterprise | Enterprise | Microservice/API | Medium projects |
Error Handling and Type Safety #
Modern PHP is far stricter about types than PHP 5 — but it needs to be configured correctly.
<?php
declare(strict_types=1); // REQUIRED in every file for strict type checking
// ANTI-PATTERN: without type declarations — PHP silently coerces types
function add($a, $b) {
return $a + $b;
}
add("5", 3); // PHP silently converts "5" to 5 — works but risky
// CORRECT: with type declarations and strict_types
function addNumbers(int $a, int $b): int {
return $a + $b;
}
// addNumbers("5", 3); // TypeError with strict_types=1
// Modern error handling — Exception hierarchy
function fetchUser(int $id): array
{
if ($id <= 0) {
throw new InvalidArgumentException("ID must be positive, given: $id");
}
$user = $this->db->find($id);
if ($user === null) {
throw new RuntimeException("User with ID $id not found");
}
return $user;
}
// try-catch-finally
try {
$user = fetchUser($idFromRequest);
processUser($user);
} catch (InvalidArgumentException $e) {
// Input error — log and return 400
$this->logger->warning($e->getMessage());
return response()->json(['error' => $e->getMessage()], 400);
} catch (RuntimeException $e) {
// Data error — log and return 404
$this->logger->info("User not found", ['id' => $idFromRequest]);
return response()->json(['error' => 'Not found'], 404);
} finally {
// Always executed — cleanup, logging, etc.
$this->db->releaseConnection();
}
When to Choose PHP #
PHP isn’t the choice for every scenario, but there are domains where PHP remains highly competitive.
Choose PHP if:
✓ You're building a CMS or content platform (WordPress ecosystem)
✓ Your team already has deep PHP expertise
✓ You need cheap, easy hosting — PHP is available on almost all shared hosting
✓ E-commerce projects with Laravel or Magento
✓ Web applications with common requirements — CRUD, auth, dashboards
✓ You need a very mature package ecosystem for the web
Consider alternatives if:
✗ You're building real-time applications with many concurrent connections → Node.js, Go
✗ You need high performance for CPU-intensive computation → Go, Rust
✗ Microservices that need very fast startup times → Go, Rust
✗ The team is more familiar with another language and there's no PHP-specific need → follow the team's expertise
✗ Mobile backends needing consistently <1ms response times → Go
✗ ML/AI pipelines → Python
flowchart TD
A{What's the main need?} --> B{Web app / CMS?}
A --> C{Real-time / heavy WebSocket?}
A --> D{CPU-intensive?}
A --> E{WordPress ecosystem?}
B -- Yes, PHP team --> F[PHP + Laravel ✓]
B -- Yes, new team --> G[Consider Go or Python]
C -- Yes --> H[Node.js / Go fits better]
D -- Yes --> I[Go / Rust fits better]
E -- Yes --> J[PHP is irreplaceable ✓]
E -- No --> K[Evaluate other languages]FAQ #
Is PHP dead?
No. PHP still powers more than 75% of all websites on the internet, including WordPress, which itself is used by more than 40% of the world’s websites. PHP 8.x is actively developed with minor releases every year and major releases every 3–4 years. The community remains large, and the Laravel ecosystem keeps growing.
What’s the difference between PHP-FPM and Apache mod_php?
mod_php runs PHP as a module inside the Apache process — every Apache worker carries the PHP interpreter. PHP-FPM (FastCGI Process Manager) is a separate process communicating with the web server via FastCGI. PHP-FPM is more efficient for high traffic because it can be managed independently of the web server, supports flexible pool configuration, and can be used with Nginx.
When should I use declare(strict_types=1)?
Always — for every PHP file you write. Without strict_types=1, PHP silently performs type coercion that can hide bugs. With strict types, a function declared to accept int will throw a TypeError if given a string — even a numeric string like "42". This is far more predictable.
Does PHP support async programming?
PHP supports async through third-party libraries like ReactPHP, Amp, and Swoole. PHP 8.1 added Fibers as a language-level primitive that lets these libraries work more efficiently. However, for most regular web applications using PHP-FPM, async isn’t relevant — every request is already isolated and PHP-FPM handles concurrency at the process pool level.
What is PSR and why does it matter?
PSR (PHP Standards Recommendations) are standards issued by PHP-FIG (Framework Interop Group). The most important PSRs are PSR-4 (autoloading), PSR-7 (HTTP message interfaces), and PSR-12 (coding style). Following PSR ensures your code is compatible with the broad package ecosystem and easy for other PHP developers to understand.
Summary #
- PHP is a server-side language with a share-nothing architecture — every request executes as a fresh process with no persistent state. This model makes PHP easy to scale horizontally but differs in paradigm from long-running-process languages like Go or Node.js.
- PHP 7.0 was the performance game changer — twice as fast as PHP 5.6 with significant memory reduction. If projects are still on PHP 5.x, migrating to PHP 8.x is a priority.
- PHP 8.x brings modern language features — JIT compiler, named arguments, match expressions, union types, enums, readonly properties, and fibers make PHP 8.x a far more expressive and safe language than PHP 5.
declare(strict_types=1)is mandatory in every file — without it, PHP silently coerces types, hiding bugs. Enable strict types for all new code.- Composer is the foundation of the modern PHP ecosystem — there’s no reason to write a PHP application without Composer in 2024. Packagist provides thousands of ready-to-use packages.
- Laravel for productivity, Symfony for enterprise flexibility — both are excellent choices with different trade-offs. For new projects with small-to-medium teams, Laravel is a very solid starting point.
- PHP remains irreplaceable in the WordPress ecosystem — more than 40% of the internet runs on WordPress. If your work touches WordPress, WooCommerce, or plugin development, PHP has no substitute.
- Native enums (PHP 8.1) replace class constants — use enums for all limited, well-defined values. More type-safe, can have methods, and integrate with pattern matching.
Next: Installation →