PHP Extension Ecosystem — A Map of Core, Bundled, and Common Extensions
Build a practical 'what extension do I reach for' map across the extensions that show up constantly in real PHP applications -- SPL, PDO, Reflection, Streams, PCRE, OPcache, mbstring, Intl, cURL, XML, SQLite, MySQL, PostgreSQL, Sodium, GD, Phar, FFI, PCNTL, and Sockets -- covering, concisely, each one's purpose, how it gets enabled, its typical APIs, common use cases, platform considerations, and security/performance notes.
PHP's core language (control structures, the type system, functions, classes) is deliberately small; almost everything that makes PHP practically useful for real applications -- talking to a database, handling images, parsing regular expressions, doing cryptography -- comes from EXTENSIONS, some bundled and enabled by default, some bundled but requiring explicit enabling, and some entirely separate installs. This lesson is intentionally broad rather than deep: several of these extensions already have dedicated lessons elsewhere in this course, and the goal here is a reference map you can scan to answer 'what extension handles X' quickly, with just enough detail on installation and security considerations to know what to check before relying on one.
Core Data-Structure and Introspection Extensions
These four are either always present or present in the overwhelming majority of real PHP installations, and form the backbone of how PHP code introspects and structures itself.
• SPL (Standard PHP Library) -- bundled and enabled by default, no php.ini entry needed. Purpose: built-in interfaces and data-structure classes (Iterator, ArrayAccess, Countable, SplStack, SplQueue, SplObjectStorage, SplFixedArray) giving the engine and userland code a shared vocabulary for iteration and collections. Typical use: custom collection classes implementing IteratorAggregate/Countable/ArrayAccess, SplObjectStorage for keying data by object identity, spl_autoload_register() for autoloading. No meaningful security surface; performance is generally on par with or better than equivalent hand-rolled structures for the access pattern each SPL class targets. See the dedicated SPL lesson for depth. • Reflection -- bundled and always enabled; cannot be disabled. Purpose: runtime introspection of classes, methods, properties, parameters, and attributes -- the exact mechanism that makes attributes, dependency injection containers, ORMs, and serializers work by inspecting code structure instead of requiring manual registration. Typical APIs: ReflectionClass, ReflectionMethod, ReflectionProperty, ReflectionAttribute. Performance: reflection calls are meaningfully slower than direct code paths and should be CACHED (most frameworks cache resolved ReflectionClass data) rather than re-reflected on every request. See the dedicated Reflection lesson for depth. • Streams -- a core PHP abstraction (not a separately enable/disable-able extension) unifying files, network sockets, compressed archives, and in-memory buffers (php://memory, php://temp) behind one fopen()/fread()/fwrite() API, extensible via user-defined stream wrappers and filters. Typical use: reading a remote URL via fopen('https://...') (governed by allow_url_fopen), a custom protocol handler, on-the-fly compression via a stream filter. Security: allow_url_fopen combined with unchecked user input is a classic SSRF/remote-file-inclusion vector -- disable it or validate URLs strictly. See the dedicated Streams lesson for depth. • PCRE (Perl-Compatible Regular Expressions) -- bundled and enabled by default; the preg_* functions (preg_match, preg_replace, preg_split) wrap the bundled PCRE2 library directly. Typical use: pattern validation, text extraction, search-and-replace beyond what str_* functions can express. Performance/security: a poorly written pattern (nested quantifiers especially) run against attacker-controlled input can trigger catastrophic backtracking -- a genuine denial-of-service vector (ReDoS); pcre.backtrack_limit bounds the damage, and untrusted input should go through simple, well-tested patterns rather than exotic regexes.
Database Access Extensions
Relational database connectivity in PHP is spread across a driver-agnostic layer (PDO) and vendor-specific extensions underneath it, plus a couple of self-contained options that need no separate server at all.
• PDO (PHP Data Objects) -- bundled by default as the abstraction layer, though each database driver (pdo_mysql, pdo_pgsql, pdo_sqlite) is its own extension enabled separately, usually shipped and turned on by the OS package (e.g. php-mysql). Purpose: one consistent, driver-agnostic API (prepare/execute/fetch, transactions, named/positional bound parameters) so query code doesn't have to be rewritten when the underlying database vendor changes. Security: always use prepared statements with bound parameters -- never string-interpolate values into SQL -- to prevent SQL injection. See the dedicated SQL/PDO lessons for depth. • MySQL (mysqli / pdo_mysql) -- not bundled by default; requires extension=mysqli or extension=pdo_mysql plus the corresponding client library, commonly installed via the OS package manager or a Docker base image variant. mysqli offers a MySQL-specific API (including multi-statement/multi-result support); pdo_mysql offers the vendor-neutral PDO API on top of the same underlying connection. Security: both support prepared statements -- use them exclusively for any query touching user input. • PostgreSQL (pgsql / pdo_pgsql) -- not bundled by default; requires extension=pgsql or extension=pdo_pgsql plus libpq on the system. Purpose: connect to PostgreSQL, with the procedural pg_* functions exposing PostgreSQL-specific features (LISTEN/NOTIFY, native array and JSONB column types, COPY) beyond what the generic PDO layer surfaces. Typical use case: applications relying on PostgreSQL-specific data types or its stronger constraint/transactional guarantees. Security: identical prepared-statement guidance as MySQL applies. • SQLite (sqlite3 / pdo_sqlite) -- usually bundled, since SQLite itself is a small embedded C library with no separate server process. Purpose: a full SQL database living in a single file (or purely in memory) with zero server setup, ideal for local development, test fixtures, embedded/CLI tools, and small-to-medium applications without heavy write concurrency needs. Platform/perf consideration: SQLite serializes writes at the file level, making it a poor fit for high-write-concurrency web applications running many simultaneous PHP-FPM workers -- reach for MySQL/PostgreSQL once write concurrency actually matters.
Text, Encoding, and Internationalization Extensions
Correctly handling non-ASCII text -- encoding-correctly and culture-correctly, which are genuinely different concerns -- and structured document formats both live here.
• mbstring -- usually bundled and enabled, though technically a separate extension (extension=mbstring). Purpose: correct string handling for multi-byte encodings, especially UTF-8, since PHP's native str_* functions are byte-oriented and mis-count or mis-slice multi-byte characters. Typical APIs: mb_strlen(), mb_substr(), mb_convert_encoding(), mb_detect_encoding(). Use case: any application handling non-ASCII text should use mb_* consistently over str_* for length/position operations. See the dedicated Unicode/mbstring lesson for depth. • Intl -- wraps the ICU (International Components for Unicode) library; not always installed by default (extension=intl, requiring the system libicu package). Purpose: locale-aware formatting and comparison, going beyond mbstring's encoding-correctness into culture-correctness -- number/currency/date formatting per locale (NumberFormatter, IntlDateFormatter) and locale-aware string collation (Collator) for correctly alphabetizing text across languages. Platform note: exact formatting output can shift slightly across ICU library versions on different OS/container images -- pin or verify the ICU version if formatting output must be stable. • XML -- actually a family of extensions (the libxml core, DOM, SimpleXML, XMLReader/XMLWriter), usually bundled since they wrap the widely available libxml2 library. Purpose: parsing, building, transforming, and querying XML -- DOM for a full mutable tree with XPath support, SimpleXML for quick read access with a friendly object syntax, XMLReader/Writer for streaming huge documents without loading them fully into memory. Security: always disable external entity loading when parsing untrusted XML, to prevent XXE (XML External Entity) attacks that can read local files or trigger outbound requests from the server.
Performance, Systems, and Native-Interop Extensions
These extensions reach outside PHP's normal request-response model -- into caching compiled code, calling native libraries directly, or working with OS-level process and network primitives.
• OPcache -- bundled with PHP but must be explicitly enabled (zend_extension=opcache plus opcache.enable=1), since caching precompiled bytecode across requests is significant enough behavior that it's opt-in rather than silently always-on, though most modern OS distributions and Docker images enable it by default. Typical config: opcache.memory_consumption, opcache.validate_timestamps, opcache.jit. Performance: arguably the single highest-impact production PHP setting there is, since it skips re-parsing and re-compiling PHP source on every request. See the dedicated OPcache/JIT lesson for depth. • FFI (Foreign Function Interface) -- bundled since PHP 7.4 but disabled for web requests by default (ffi.enable=preload), meaning it's normally only usable from CLI or explicitly preloaded contexts in production, deliberately. Purpose: call functions and read/write structs in an already-compiled C shared library directly from PHP, via a C header string parsed at runtime, with no PHP extension build pipeline needed. Security: ffi.enable=true on a web-facing environment is a significant attack-surface increase -- close to an arbitrary native-code-execution capability -- keep it off outside trusted CLI/preload contexts. See the dedicated FFI lesson and the Extension Development lesson's FFI-vs-extension comparison for depth. • PCNTL (Process Control) -- bundled on Unix-like systems only (unavailable on Windows entirely), and only meaningful in CLI SAPI context, never inside PHP-FPM/web requests. Purpose: fork child processes (pcntl_fork()), handle POSIX signals (pcntl_signal(), pcntl_async_signals(true)), and wait on a child's exit status (pcntl_wait()) -- the mechanism behind graceful shutdown handling in long-running workers and simple multi-process parallelism in CLI scripts. Platform consideration: guard any PCNTL usage (function_exists('pcntl_fork')) or restrict it strictly to worker/CLI entry points. • Sockets -- bundled but must be explicitly enabled (extension=sockets); provides a low-level BSD-socket-style API (socket_create(), socket_bind(), socket_listen(), socket_select()) below the level of the Streams abstraction, with direct control over socket options, non-blocking I/O, and protocols Streams doesn't conveniently expose (raw UDP, multicast). Typical use case: building a lightweight custom TCP/UDP server or client, or the transport layer underneath a higher-level async runtime. For most application-level networking (HTTP calls, simple TCP clients), the higher-level Streams or cURL APIs are simpler and sufficient.
Security, Files, and Media Extensions
Cryptography, image processing, packaging PHP applications for distribution, and outbound HTTP round out the common set.
• cURL -- wraps libcurl; usually bundled but requires the system libcurl-dev headers at PHP build time and extension=curl enabled. Purpose: a full-featured outbound HTTP(S) (and other protocol) client with fine control over headers, timeouts, redirects, TLS verification, and multiplexed/async requests via curl_multi_*. Typical APIs: curl_init(), curl_setopt(), curl_exec(), curl_multi_init(). Security: never disable CURLOPT_SSL_VERIFYPEER/VERIFYHOST in production, and always set explicit timeouts to avoid a hung upstream blocking a PHP-FPM worker indefinitely. • Sodium -- bundled and enabled by default since PHP 7.2 (extension=sodium, wrapping libsodium). Purpose: modern, misuse-resistant cryptography -- authenticated encryption (sodium_crypto_secretbox), public-key encryption and signing (crypto_box, crypto_sign), and secure random bytes. Security note: Sodium's API is deliberately narrow and hard to misuse compared to older approaches -- prefer it over hand-rolled crypto or the legacy mcrypt extension (removed from PHP entirely in 7.2, precisely because Sodium replaced it). • GD -- a common but not-always-bundled image manipulation extension (extension=gd, typically needing system libgd/libjpeg/libpng dev packages at PHP build time). Purpose: create, resize, crop, watermark, and convert raster images (JPEG/PNG/GIF/WebP) in pure PHP. Typical APIs: imagecreatefromjpeg(), imagecopyresampled() for resizing, imagepng()/imagejpeg() for output. Performance/platform: image processing is CPU- and memory-heavy relative to typical request work -- resizing large uploads synchronously inside a web request can exhaust PHP-FPM workers under load; heavy image work is often better pushed to a background queue job. • Phar (PHP Archive) -- bundled by default; packages an entire application (code, assets, even a bundled SQLite database) into a single executable .phar file. Typical use case: distributing CLI tools as one file with no separate install step (Composer, PHPUnit, and PHP-CS-Fixer are all commonly distributed this way). Security consideration: phar.readonly (default on) prevents a running script from writing/modifying phar archives, and deserializing data read from an untrusted .phar can trigger PHP object injection via the phar's own metadata -- treat an untrusted .phar exactly like untrusted serialized input.
Code Examples
This ties five extensions from the lesson into one small script: SPL for a fixed-size typed collection, mbstring for correct multi-byte length, Sodium for secure random tokens, PDO+SQLite for a zero-setup database query with no separate server, and Reflection to introspect the PDO object without hardcoding its class name -- exactly the kind of everyday combination real PHP applications lean on constantly, mostly without thinking about which 'extension' each piece technically belongs to. Expected result: 8 32 1 PDO pdo_sqlite: loaded mbstring: loaded sodium: loaded php -m is the fastest way to confirm whether an extension this lesson describes as 'usually bundled' or 'not bundled by default' is actually present on a SPECIFIC server, container image, or shared host -- 'usually' and 'commonly' throughout this lesson are exactly the kind of assumption worth verifying directly rather than trusting blindly, especially before deploying code that depends on one.
<?php
declare(strict_types=1);
// SPL: a strongly-sized, typed-by-convention fixed collection
$scores = new SplFixedArray(3);
$scores[0] = 95;
$scores[1] = 88;
$scores[2] = 76;
// mbstring: correct character length for multi-byte text
$name = 'Francois';
echo mb_strlen($name) . PHP_EOL;
// Sodium: cryptographically secure random token, bundled by default
$token = bin2hex(random_bytes(16));
echo strlen($token) . PHP_EOL; // 32 hex chars from 16 random bytes
// PDO + SQLite: a driver-agnostic query against a zero-setup embedded database
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
$stmt = $pdo->prepare('INSERT INTO users (name) VALUES (:name)');
$stmt->execute(['name' => $name]);
$row = $pdo->query('SELECT COUNT(*) AS total FROM users')->fetch(PDO::FETCH_ASSOC);
echo $row['total'] . PHP_EOL;
// Reflection: introspect what was just built, without needing to know its
// class name in advance
$class = new ReflectionClass($pdo);
echo $class->getName() . PHP_EOL;
// A quick "is it actually available" check before relying on an extension
foreach (['pdo_sqlite', 'mbstring', 'sodium'] as $ext) {
echo $ext . ': ' . (extens
…# List every extension the current PHP binary/SAPI has loaded:
php -m
# Check one specific extension:
php -m | grep -i gd
# Full build/config details, including where extension_dir points:
php -i | grep extension_dir
# Enable a bundled-but-off-by-default extension (e.g. sockets) on Debian/Ubuntu:
sudo phpenmod sockets
sudo systemctl restart php8.3-fpmImportant Rules
Never assume an extension is available just because it's common -- check extension_loaded() or php -m, especially on unfamiliar hosting/container environments.
Prefer PDO with bound parameters over any database extension's raw query interpolation, for every extension in the database group.
mbstring's mb_* functions for length/position on any text that might contain non-ASCII characters -- never plain str_* functions for that purpose.
Disable external entity loading before parsing any untrusted XML input, regardless of which XML extension is being used.
Keep ffi.enable and allow_url_fopen off in web-facing production environments unless a specific, well-understood need requires them.
Push CPU/memory-heavy work (GD image processing, for instance) to a background queue rather than doing it synchronously inside a web request.
Why This Matters in Production
A deploy that works locally and fails only in production is very often an extension mismatch -- a developer's local PHP install has GD or Intl enabled by default (common on a full desktop install) while the production Docker image was built from a minimal base that doesn't include them, and the failure only surfaces the first time that specific code path runs against real traffic. Declaring extension dependencies explicitly (composer.json's require: {'ext-gd': '*'}) and verifying them in CI against the SAME base image used in production turns this from a production incident into a failed build, days earlier and far cheaper to fix.
// assuming GD and mysqli are just 'part of PHP' and will always be present imagecreatefromjpeg($path); // Fatal error on a minimal PHP install without ext-gd if (!extension_loaded('gd')) { throw new RuntimeException('The gd extension is required but not installed.'); } imagecreatefromjpeg($path);
Unlike SPL, Reflection, or PCRE (which are effectively always present), extensions like GD, mysqli, and Intl are commonly but NOT universally installed -- a minimal PHP install, a stripped-down Docker base image, or a shared host may not have them. Checking extension_loaded() (or declaring the dependency explicitly via composer.json's require ext-gd) fails predictably and early instead of with a confusing fatal error deep inside application logic.
[multiple-choice] Which extension is always enabled and cannot be disabled, forming the mechanism behind attributes, DI containers, and ORMs reading code structure at runtime? A. GD B. Reflection C. Sockets D. Phar
B. Reflection — Reflection is bundled and always enabled -- it cannot be disabled -- and is the runtime introspection mechanism that lets frameworks read classes, methods, properties, and attributes without requiring manual registration.
[multiple-choice] Why should a large, CPU-heavy image resize using GD generally avoid running synchronously inside a web request? A. GD cannot be called from a web request at all B. Image processing is CPU- and memory-heavy relative to typical request work, and can exhaust PHP-FPM workers under load if done synchronously -- it's often better pushed to a background queue job C. GD requires PCNTL to function D. GD only works from the CLI SAPI
B. Image processing is CPU- and memory-heavy relative to typical request work, and can exhaust PHP-FPM workers under load if done synchronously -- it's often better pushed to a background queue job — A synchronous, heavy image resize ties up a PHP-FPM worker for the whole operation, and under load that can exhaust the worker pool -- exactly the kind of saturation signal covered in the Observability lesson -- which is why heavy image work is commonly moved to a background queue instead.
[multiple-choice] What security risk does disabling external entity loading before parsing untrusted XML protect against? A. SQL injection B. XXE (XML External Entity) attacks that can read local files or trigger outbound requests from the server C. Cross-site scripting (XSS) D. ReDoS from catastrophic regex backtracking
B. XXE (XML External Entity) attacks that can read local files or trigger outbound requests from the server — XML parsers that resolve external entities by default can be tricked, via a maliciously crafted XML document, into reading arbitrary local files or making outbound network requests from the server -- disabling external entity loading before parsing untrusted XML closes this specific attack path.
An application needs to: (1) validate and reformat user-submitted URLs, (2) securely hash an API key for storage, (3) resize a user-uploaded profile photo, and (4) parse an uploaded XML data-export file from a partner. For each, name the extension from this lesson you'd reach for and one specific security or platform consideration to keep in mind.
(1) URL validation/reformatting -- the URI extension (PHP 8.5+) or careful parse_url() handling on earlier versions; watch for WHATWG-vs-RFC-3986 parsing disagreements on edge cases. (2) Hashing an API key -- Sodium (or password_hash() for password-specific storage); prefer it over hand-rolled crypto given its narrower, harder-to-misuse API. (3) Resizing a profile photo -- GD; do the resize in a background queue job rather than synchronously in the web request, since image processing is CPU/memory-heavy. (4) Parsing a partner's XML file -- the XML/DOM extension family; explicitly disable external entity loading before parsing, since the file comes from outside the application and XXE is a real risk on any untrusted XML input.
Match each task to the section it belongs to, and recall the one security/platform note called out for that extension.