PHP (8.5) Module 1 – Start here: PHP in 2026
What PHP is and where it runs in 2026
How PHP runs from the command line and behind web servers, why every request starts with empty memory, and which PHP branches still get security fixes.
What you will learn
- Explain how the php command and a web server start the same script, and why each web request starts with empty memory
- Choose a way to serve PHP on the web from PHP-FPM, the Apache module, the built-in server and FrankenPHP
- Find which PHP branch a site runs and check whether that branch still receives security fixes
- Identify the PHP version, system, integer size and SAPI behind a recorded run
What you’ll learn
What you will learn
- Explain how the php command and a web server start the same script, and why each web request starts with empty memory
- Choose a way to serve PHP on the web from PHP-FPM, the Apache module, the built-in server and FrankenPHP
- Find which PHP branch a site runs and check whether that branch still receives security fixes
- Identify the PHP version, system, integer size and SAPI behind a recorded run
On this page
PHP is the server-side language of 69.4 % of the websites whose language W3Techs can identify, and by the same survey about half of those PHP sites run a branch that no longer receives security fixes. Anyone who looks after a PHP site needs to know three things: how PHP runs their code, what PHP keeps from one request to the next, and which branch they run.
This lesson runs one small script from the command line and through a web server and watches what changes. It then compares the common ways to serve PHP, works out which branches still get fixes, and shows how to read the PHP behind every recorded run in this track.
One PHP engine, started by different programs
Type php hello.php in a terminal and PHP runs the file once, prints what it echoes and stops. Put the same file
behind a web server and PHP runs it once for every request, and what it echoes becomes the page. Both times the same
engine compiles and runs the script. What differs is the program around it that starts the engine and hands it its
input, which PHP calls a SAPI: Server API: the part of PHP that connects its engine to the program that starts it, such as the php command (cli), PHP-FPM (fpm-fcgi), the Apache module or the built-in web server (cli-server). PHP_SAPI holds its name., short for Server API.
The php command is the CLI SAPI. It runs scripts, scheduled jobs and tools such as Composer. Web servers reach PHP
through other SAPIs: PHP-FPM, the Apache module, the built-in development server and application servers such as
FrankenPHP. A script can ask which one started it, because the constant PHP_SAPI holds its name. This program
prints that and the other facts a run depends on:
<?php
// Which PHP runs this script, on which system, and through which SAPI?
printf("PHP version %s\n", PHP_VERSION);
printf("Branch %d.%d\n", PHP_MAJOR_VERSION, PHP_MINOR_VERSION);
printf("System %s\n", PHP_OS_FAMILY);
printf("Integer size %d bits\n", PHP_INT_SIZE * 8);
printf("SAPI %s\n", PHP_SAPI); Output
PHP version 8.5.11 Branch 8.5 System Darwin Integer size 64 bits SAPI cli
Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php version_check.php
The php command ran it, so the SAPI is cli. The next session runs one small file twice: first with the php
command, then through the built-in web server: The development web server that php -S starts. It runs scripts through the cli-server SAPI, logs each request in its terminal and must not be used on a public network. More in “Your first PHP script, the CLI and error messages” that php -S starts. A second
terminal asks that server for the page with curl, a command-line program that makes web requests:
<?php
echo 'Run by the ', PHP_SAPI, " SAPI\n";
echo 'Request method: ', $_SERVER['REQUEST_METHOD'] ?? 'none, no web request', "\n"; # The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import run, serve
run("php public/sapi.php")
serve("php -S 127.0.0.1:8471 -t public", """
curl -s http://127.0.0.1:8471/sapi.php
curl -s -I http://127.0.0.1:8471/sapi.php
""") Output
$ php public/sapi.php Run by the cli SAPI Request method: none, no web request # Terminal 1: start the server $ php -S 127.0.0.1:8471 -t public # Terminal 2: send requests $ curl -s http://127.0.0.1:8471/sapi.php Run by the cli-server SAPI Request method: GET $ curl -s -I http://127.0.0.1:8471/sapi.php HTTP/1.1 200 OK Host: 127.0.0.1:8471 Date: … Connection: close X-Powered-By: PHP/8.5.11 Content-type: text/html; charset=UTF-8 # Terminal 1 showed this log, then Ctrl+C stopped the server: […] PHP 8.5.11 Development Server (http://127.0.0.1:8471) started […] 127.0.0.1:<port> Accepted […] 127.0.0.1:<port> [200]: GET /sapi.php […] 127.0.0.1:<port> Closing […] 127.0.0.1:<port> Accepted […] 127.0.0.1:<port> [200]: HEAD /sapi.php […] 127.0.0.1:<port> Closing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sapi_session.py
The file did not change, but its surroundings did. Under the built-in server PHP_SAPI is cli-server, and the
An array that PHP fills before a script starts, with facts about how it was started: under a web SAPI the request (its method, path and headers), on the command line the script's name and its arguments. array
describes the request: its REQUEST_METHOD is GET, because a web request came with the run. On the command line
there was no request, so the
The null coalescing operator: a ?? b gives the value of a when it exists and is not null, and b otherwise, without a warning about a missing key. printed
the text on its right. The server logged each connection in its own terminal, and the HEAD request that curl -I
sends, which asks for the headers alone, shows the headers the server sent, among them X-Powered-By: PHP/8.5.11.
Step through the two runs:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Two ways in. The session runs one file, public/sapi.php, twice. The terminal on the left starts it as a program; curl on the right asks a web server for it.
- The php command. Typing php public/sapi.php starts the CLI SAPI. It reads the file and hands it to the engine with no web request around it.
- The engine runs the file. The engine compiles the file and runs it. PHP_SAPI is cli, and $_SERVER has no REQUEST_METHOD, so the fallback text after ?? is printed.
- A web request. In a second terminal, curl sends GET /sapi.php to the built-in server started with php -S 127.0.0.1:8471 -t public.
- The same file, a new SAPI. php -S finds sapi.php in its document root, public, puts the request into $_SERVER and starts the engine on the same file.
- The answer and the log. The engine prints the cli-server SAPI and GET. The server sends that back as the response body and writes the request to its log in its own terminal.
Other web SAPIs report their own names: fpm-fcgi under PHP-FPM and apache2handler under the Apache module. The
manual’s page on the command line recommends exactly this test, the SAPI’s name, when a script must know whether it
runs from a terminal.
Checkpoint
Check: One engine, several SAPIs
Every web request starts with empty memory
Ask a PHP page for the same address three times, and a counter in it might be expected to reach 3. Under PHP-FPM,
the Apache module and the built-in server it never does, because each request runs the script from its first line
with nothing left from earlier requests. This script tries to count in three places, a variable, a static
variable inside a function and a file:
<?php
// If PHP kept memory from one request to the next, these two would count up.
$visits = ($visits ?? 0) + 1;
function calls(): int
{
static $count = 0;
return ++$count;
}
// A file outlives the request that wrote it.
$file = __DIR__ . '/../visits.txt';
$saved = (is_file($file) ? (int) file_get_contents($file) : 0) + 1;
file_put_contents($file, (string) $saved);
echo "variable: $visits, static: " . calls() . ", file: $saved\n"; # The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import serve
serve("php -S 127.0.0.1:8471 -t public", """
curl -s http://127.0.0.1:8471/counter.php
curl -s http://127.0.0.1:8471/counter.php
curl -s http://127.0.0.1:8471/counter.php
""") Output
# Terminal 1: start the server $ php -S 127.0.0.1:8471 -t public # Terminal 2: send requests $ curl -s http://127.0.0.1:8471/counter.php variable: 1, static: 1, file: 1 $ curl -s http://127.0.0.1:8471/counter.php variable: 1, static: 1, file: 2 $ curl -s http://127.0.0.1:8471/counter.php variable: 1, static: 1, file: 3 # Terminal 1 showed this log, then Ctrl+C stopped the server: […] PHP 8.5.11 Development Server (http://127.0.0.1:8471) started […] 127.0.0.1:<port> Accepted […] 127.0.0.1:<port> [200]: GET /counter.php […] 127.0.0.1:<port> Closing […] 127.0.0.1:<port> Accepted […] 127.0.0.1:<port> [200]: GET /counter.php […] 127.0.0.1:<port> Closing […] 127.0.0.1:<port> Accepted […] 127.0.0.1:<port> [200]: GET /counter.php […] 127.0.0.1:<port> Closing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 requests_session.py
Only the file counted. When a request ends, PHP frees everything the script created: variables, static variables and objects. The next request may even reach the same process: PHP-FPM keeps worker processes that each serve many requests in turn, and the manual’s page on persistent connections describes how a later page served by the same child process can reuse its database connection. Even then the script starts with empty memory. This is the shared-nothing: PHP's classic request model: each web request runs the script with empty memory, and PHP frees everything the script made when the request ends, so data that must last is kept in a database, a file, a cookie or a session. model, and it is why PHP sites keep whatever must last in a database, a file, a cookie or a session. It has a benefit too: a request that goes wrong cannot leave a broken value behind for the next visitor. Step through the three requests:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- Request 1 arrives. curl asks for /counter.php. PHP starts the script from its first line with empty memory, so $visits is unset and the static $count is 0.
- The file is read. There is no visits.txt yet, so the script counts from 0 and writes 1 to the file.
- The answer. The script prints variable: 1, static: 1, file: 1, and the server sends it back.
- The request ends. PHP frees everything the script made, $visits and the static $count included. Only the file is left.
- Request 2. The second request starts from empty memory again. $visits is unset and $count is 0, but the file still holds 1.
- Only the file counts up. The script reads 1 from the file and writes 2, while the variable and the static variable start again at 1.
- The second answer. It prints variable: 1, static: 1, file: 2, and the memory is freed again when the request ends.
- Request 3. The third request goes the same way and prints variable: 1, static: 1, file: 3. Data that must outlive a request needs a file, a database, a cookie or a session.
Some ways of running PHP keep memory on purpose. FrankenPHP’s worker mode: A way of serving PHP in which a long-running process boots the application once and handles many requests with it, so global and static variables stay in memory from one request to the next. boots the application once and handles many requests with it, which saves the start-up work of each request. Its documentation lists what then survives from one request to the next: static variables, static properties of classes, global variables of the worker script and settings changed at run time. Code written for the classic model can then carry one user’s data into another user’s response, so it needs checking before it runs that way.
Key idea
In classic PHP every request starts with empty memory and leaves nothing behind, so whatever must last goes to a database, a file, a cookie or a session.
Checkpoint
Check: Empty memory for every request
Four common ways to serve PHP on the web
A web server on its own sends files. To answer requests for PHP pages, something must run PHP for it, and four arrangements cover most sites:
- PHP-FPM. PHP-FPM: The FastCGI Process Manager: a service that keeps pools of PHP worker processes and runs scripts for a web server such as Nginx or Apache httpd, which pass it each request over the FastCGI protocol., the FastCGI Process Manager, keeps pools of PHP worker processes and runs scripts for a web server such as Nginx or Apache httpd. The manual’s Apache page recommends it for every modern deployment. FPM itself must never be reachable from an untrusted network: the manual warns that a client that can send it FastCGI requests can run code.
- The Apache module. mod_php runs PHP inside Apache httpd’s own processes. Unless PHP is built thread safe it needs Apache’s prefork mode, which limits how many requests run at once, and the manual now says not to use it for new installations.
- The built-in server.
php -Sserves a folder from your own computer with one command. It handles one request at a time unlessPHP_CLI_SERVER_WORKERSstarts more workers, and the manual says it should not be used on a public network. - FrankenPHP. An application server written in Go and C that embeds PHP in the Caddy web server. It runs an existing app as it is, starting each request fresh on one of its PHP threads, or keeps the booted app in memory in worker mode.
Which one fits depends on the site. Answer the three questions about one you have in mind; the table marks what fits and gives the reason under each one that does not:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Way to serve PHP | On a public site | Many requests at once | Empty memory per request | Setup |
|---|---|---|---|---|
| PHP-FPM How it runs PHP: a pool of PHP processes behind a web server | Good the way the manual names for modern deployments; keep FPM off untrusted networks | Good a pool of worker processes takes requests from Nginx or Apache httpd | Good every request starts with empty memory | Mixed a web server and an FPM pool to configure |
| Apache module How it runs PHP: PHP inside Apache httpd, mod_php | Mixed works, but the manual says not to use mod_php for new installations | Poor needs Apache's prefork mode unless PHP is thread safe, which limits concurrency | Good every request starts with empty memory | Good one module loaded into Apache httpd |
| Built-in server How it runs PHP: php -S, one process | Poor for development only: the manual says not to use it on a public network | Poor one request at a time unless PHP_CLI_SERVER_WORKERS starts more | Good every request starts with empty memory | Good one command, php -S, from the project folder |
| FrankenPHP How it runs PHP: PHP built into the Caddy web server | Good an application server that embeds PHP in the Caddy web server | Good runs requests in parallel on PHP threads, about two per CPU by default | Good serves an existing app as it is, each request fresh | Mixed replaces the web server, so the site moves to Caddy |
| FrankenPHP worker mode How it runs PHP: the booted app kept in memory | Good the same server, with the app booted once | Good skips booting the app again for every request | Poor global and static variables survive from one request to the next | Poor the code must reset its own state between requests |
How the answers decide
- “Who will open the site?” Anyone, on a public network: rules out Built-in server. php -S is for development: the manual says it should not be used on a public network.
- “Who will open the site?” Only me, on my own computer: adds a caveat to PHP-FPM and Apache module. These work on your own computer too, but need a web server set up beside PHP, where php -S needs one command.
- “Must PHP run with an Apache httpd that the server already has?” Yes, Apache stays: adds a caveat to Apache module. It fits an Apache that is already there, but the manual says not to use mod_php for new installations: put PHP-FPM behind Apache instead.
- “Must PHP run with an Apache httpd that the server already has?” Yes, Apache stays: adds a caveat to FrankenPHP and FrankenPHP worker mode. FrankenPHP is a web server itself, so it would sit beside that Apache or replace it.
- “Does the code keep data in global or static variables while it handles a request?” Yes, it does: rules out FrankenPHP worker mode. Worker mode keeps global and static variables from one request to the next, so data set for one user can reach the next one.
- “Does the code keep data in global or static variables while it handles a request?” I don't know yet: adds a caveat to FrankenPHP worker mode. Check first: in worker mode, every global and static variable the code sets is still there in the next request.
In short: for a public site, run PHP-FPM behind your web server, also when Apache httpd stays, or let FrankenPHP be
the web server; keep worker mode for code that resets its own state; use php -S only on your own computer; and
leave mod_php to existing setups.
Checkpoint
Check: Ways to serve PHP on the web
A PHP branch gets four years of fixes, then none
PHP 8.5.11 is a patch release of the 8.5 branch: A PHP minor version, such as 8.5, together with its patch releases (8.5.0, 8.5.1 and so on), which receive fixes until the branch reaches its end of life.. php.net supports each branch for about four years from its first release: about two years of active support, with regular releases that fix bugs and security problems, then two years of security fixes only, released when needed. After that the branch reaches its end of life: The point after which a PHP branch gets no more releases, not even security fixes. php.net lists the branches that have reached it on its end-of-life page. and gets no more releases of any kind. Walk through the branches a site may run today:
- 7.4
The last PHP 7 branch, end of life end of life
7.4 reached its end of life in 2022, and its last release was 7.4.33. A site on PHP 7 has received no PHP fixes since then.
- 8.1
End of life since 2025 ended end of life
8.1 brought enumerations, readonly properties and fibers. Its security fixes ended with 2025, and 8.1.34 was its last release.
- 8.2
Security fixes until 2026 ends security only
8.2 was first released in 2022 and left active support at the end of 2024. It receives security fixes, released as needed, until the end of 2026.
- 8.3
Security fixes until 2027 ends security only
8.3 added typed class constants and json_validate() in 2023. Its active support ended with 2025, and security fixes continue until the end of 2027.
- 8.4
Active support until 2026 ends active
8.4 added property hooks and asymmetric visibility in 2024. Regular releases fix its bugs until the end of 2026, then security fixes follow until the end of 2028.
- 8.5
Active support until 2027 ends active
8.5 added the pipe operator, clone with and the URI extension in 2025. Bugs are fixed until the end of 2027 and security problems until the end of 2029. This track runs on 8.5.11.
- 8.6
Being tested testing
8.6 is in release candidates, the test builds that come before a release. php.net adds its support dates to the table once it is released.
The timeline has 7 events, from 7.4 to 8.6.
The next file holds the end dates from php.net’s table and a function that works out the status of a branch on any day. A branch older than the oldest one in the table is end of life, and a branch that is not in the table yet is reported as unknown rather than guessed:
<?php
// The last day of active support and of security fixes for each supported branch, from php.net/supported-versions.php.
const SUPPORT = [
'8.2' => ['active' => '2024-12-31', 'security' => '2026-12-31'],
'8.3' => ['active' => '2025-12-31', 'security' => '2027-12-31'],
'8.4' => ['active' => '2026-12-31', 'security' => '2028-12-31'],
'8.5' => ['active' => '2027-12-31', 'security' => '2029-12-31'],
];
// Branches older than the oldest one in the table no longer receive any fixes.
const OLDEST = '8.2';
function supportStatus(string $branch, string $today): string
{
$dates = SUPPORT[$branch] ?? null;
if ($dates === null) {
return version_compare($branch, OLDEST, '<') ? 'end of life' : 'unknown branch';
}
$day = new DateTimeImmutable($today);
if ($day <= new DateTimeImmutable($dates['active'])) {
return 'active support';
}
if ($day <= new DateTimeImmutable($dates['security'])) {
return 'security fixes only';
}
return 'end of life';
} <?php
require __DIR__ . '/support.php';
foreach (['2026-12-31', '2027-01-01'] as $today) {
echo "On $today:\n";
foreach (['7.4', '8.1', '8.2', '8.3', '8.4', '8.5', '8.6'] as $branch) {
printf(" PHP %s %s\n", $branch, supportStatus($branch, $today));
}
} Output
On 2026-12-31: PHP 7.4 end of life PHP 8.1 end of life PHP 8.2 security fixes only PHP 8.3 security fixes only PHP 8.4 active support PHP 8.5 active support PHP 8.6 unknown branch On 2027-01-01: PHP 7.4 end of life PHP 8.1 end of life PHP 8.2 end of life PHP 8.3 security fixes only PHP 8.4 security fixes only PHP 8.5 active support PHP 8.6 unknown branch
Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php support_window.php
The last day of 2026 is the last day of 8.2’s security fixes and of 8.4’s active support, and one day later both change: 8.2 reaches its end of life and 8.4 receives security fixes only. 8.6 is still being tested in release candidates, so the program does not know it yet; its dates join the table when php.net publishes them.
How many sites does that affect? W3Techs counts the PHP versions that websites report. The next program combines its shares with the same table:
<?php
require __DIR__ . '/support.php';
// Shares from W3Techs' survey of websites: each major version as a percentage of the websites that use PHP, and
// each 8.x branch as a percentage of the websites that use PHP 8.
const MAJOR = ['8' => 65.1, '7' => 27.1, '5' => 7.7, '4' => 0.1];
const PHP8 = ['8.0' => 5.9, '8.1' => 15.3, '8.2' => 27.9, '8.3' => 34.8, '8.4' => 12.0, '8.5' => 4.2];
// Every branch as a percentage of all PHP websites.
$shares = [];
foreach (PHP8 as $branch => $percentOfPhp8) {
$shares[$branch] = MAJOR['8'] * $percentOfPhp8 / 100;
}
$shares += ['7' => MAJOR['7'], '5' => MAJOR['5'], '4' => MAJOR['4']];
$days = [
'2026-12-31' => 'PHP websites by what their branch receives on 2026-12-31:',
'2027-01-01' => 'The same websites a day later, if none of them upgrades:',
];
foreach ($days as $today => $heading) {
$byStatus = ['active support' => 0.0, 'security fixes only' => 0.0, 'end of life' => 0.0];
foreach ($shares as $branch => $share) {
$byStatus[supportStatus((string) $branch, $today)] += $share;
}
echo $heading, "\n";
foreach ($byStatus as $status => $share) {
printf(" %-20s %5.1f %%\n", $status, $share);
}
} Output
PHP websites by what their branch receives on 2026-12-31: active support 10.5 % security fixes only 40.8 % end of life 48.7 % The same websites a day later, if none of them upgrades: active support 2.7 % security fixes only 30.5 % end of life 66.9 %
Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php branch_shares.php
By those shares, 48.7 % of PHP websites already run a branch without security fixes. On the first day of 2027 every 8.2 site joins them, 66.9 % in all, unless they upgrade before then.
To find the branch of a site you maintain, ask the PHP that serves it. A temporary script that prints PHP_VERSION,
requested through the site and deleted straight after, answers that, and so does the X-Powered-By header that the
session above showed, as long as the expose_php setting is on.
Trusting php -v on the server
php -v in a server’s terminal reports the command line’s PHP. The website can be served by another build, such as
an older PHP-FPM that a hosting panel or a package left behind. Check the version through the website itself before
you decide anything about its upgrade.
Checkpoint
Check: What a branch still receives
Every recorded run names the PHP behind it
The programs in this track show what they printed on PHP 8.5.11, and the command to run each one on your own
computer. Read the first program’s output again: 8.5.11 is the version, in the branch 8.5; Darwin is the
operating system family of macOS, one of the values the manual lists for PHP_OS_FAMILY, with Linux, Windows, BSD,
Solaris and Unknown; integers are 64 bits wide; and cli says the php command ran it.
The size of an integer is a property of the build. The manual puts the largest integer at about two billion on 32-bit builds and about 9 × 10¹⁸ on 64-bit ones, and a number beyond the largest integer becomes a float. This program shows where that line falls on the PHP that recorded this track’s runs:
<?php
// What the size of an integer changes. A 64-bit build prints what this page shows.
echo 'PHP_INT_SIZE ', PHP_INT_SIZE, " bytes\n";
echo 'PHP_INT_MAX ', PHP_INT_MAX, "\n";
var_dump(2_147_483_647 + 1);
var_dump(3_000_000_000);
var_dump(PHP_INT_MAX + 1); Output
PHP_INT_SIZE 8 bytes PHP_INT_MAX 9223372036854775807 int(2147483648) int(3000000000) float(9.223372036854776E+18)
Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php int_limits.php
On this 64-bit build 2,147,483,648 and 3,000,000,000 are still integers, and only PHP_INT_MAX + 1 became a float.
A 32-bit build, whose PHP_INT_MAX is 2,147,483,647, prints the first two as float(2147483648) and
float(3000000000). So when your own run of an example differs from the page, compare PHP_VERSION,
PHP_INT_SIZE and PHP_SAPI before anything else.
The runs also use PHP’s built-in settings, with no php.ini file of their own. The next lesson shows how to check which settings file your own PHP reads.
Checkpoint
Check: Reading a recorded run
Practise: PHP and where it runs
The practice below covers SAPIs, request memory, the ways to serve PHP, support dates and reading a run. It opens where your answers are weakest and gives the reasoning behind every answer:
Practice that adapts to you
Practise this lesson
17 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher · Warm-up
What is a SAPI in PHP, and which SAPI runs a script started with php script.php?
A SAPI, short for Server API, is the part of PHP that connects the engine to the program that starts it and gives the script its input. The engine that compiles and runs code is the same everywhere; the SAPI differs. The php command is the CLI SAPI, so php script.php runs under cli, with the command-line arguments in $argv and no web request. A web server reaches PHP through another SAPI: fpm-fcgi under PHP-FPM, apache2handler under the Apache module, cli-server under the built-in server started with php -S. A script can read the name from the PHP_SAPI constant or php_sapi_name(), and the manual recommends that check when code must behave differently on the command line and on the web.
-
Fresher to senior
A client's online shop runs PHP 8.1 and works without problems. They would like to stay on it for another year to save the cost of an upgrade. What do you tell them, which branch do you recommend, and how would you plan the move?
-
Mid to senior
To cut response times, an app moved from PHP-FPM to an application server's worker mode. Since then a few users report seeing another user's name in the page header, but nobody can reproduce it on demand. What do you suspect, how do you confirm it, and how do you fix it?
Key takeaways
- The same PHP engine runs every script; the SAPI that starts it,
clifor thephpcommand orcli-server,fpm-fcgiandapache2handlerfor web servers, decides what input the script gets. - Under PHP-FPM, the Apache module and the built-in server, each request starts with empty memory, so data that must last goes to a database, a file, a cookie or a session; a worker mode keeps memory between requests and needs code that resets its own state.
- A public site runs PHP-FPM behind its web server or uses FrankenPHP;
php -Sis for development only, and the manual advises against mod_php for new installations. - A branch gets about two years of active support and two of security fixes only; 8.1 and older get nothing, and by W3Techs’ shares about half of PHP websites still run one of them.
- Every recorded run in this track is PHP 8.5.11 with 64-bit integers under the CLI SAPI, so
PHP_VERSION,PHP_INT_SIZEandPHP_SAPIare the first things to compare when your own run differs.
Terms in this lesson
- branch
- A PHP minor version, such as 8.5, together with its patch releases (8.5.0, 8.5.1 and so on), which receive fixes until the branch reaches its end of life.
- end of life
- The point after which a PHP branch gets no more releases, not even security fixes. php.net lists the branches that have reached it on its end-of-life page.
- PHP-FPM
- The FastCGI Process Manager: a service that keeps pools of PHP worker processes and runs scripts for a web server such as Nginx or Apache httpd, which pass it each request over the FastCGI protocol.
- SAPI
- Server API: the part of PHP that connects its engine to the program that starts it, such as the php command (cli), PHP-FPM (fpm-fcgi), the Apache module or the built-in web server (cli-server). PHP_SAPI holds its name.
- shared-nothing
- PHP's classic request model: each web request runs the script with empty memory, and PHP frees everything the script made when the request ends, so data that must last is kept in a database, a file, a cookie or a session.
- worker mode
- A way of serving PHP in which a long-running process boots the application once and handles many requests with it, so global and static variables stay in memory from one request to the next.
References
- Supported versions (The PHP Group (php.net))
- Unsupported branches (end of life) (The PHP Group (php.net))
- PHP 8.5 release announcement (The PHP Group (php.net))
- PHP 8.4 release announcement (The PHP Group (php.net))
- PHP 8.3 release announcement (The PHP Group (php.net))
- PHP 8.1 release announcement (The PHP Group (php.net))
- PHP 8.6 release schedule (The PHP Group (wiki.php.net))
- PHP Manual: php_sapi_name (The PHP Group (php.net))
- PHP Manual: Predefined constants (The PHP Group (php.net))
- PHP Manual: Differences to other SAPIs (command line) (The PHP Group (php.net))
- PHP Manual: Built-in web server (The PHP Group (php.net))
- PHP Manual: FastCGI Process Manager (FPM) (The PHP Group (php.net))
- PHP Manual: Apache 2.x on Unix systems (The PHP Group (php.net))
- PHP Manual: Description of core php.ini directives (The PHP Group (php.net))
- PHP Manual: Persistent database connections (The PHP Group (php.net))
- PHP Manual: Integers (The PHP Group (php.net))
- FrankenPHP, the modern PHP app server (The FrankenPHP project)
- FrankenPHP documentation: worker mode (The FrankenPHP project)
- FrankenPHP documentation: configuration (The FrankenPHP project)
- Usage statistics of PHP for websites (W3Techs (Q-Success))
- Usage statistics of PHP version 8 for websites (W3Techs (Q-Success))
Report a problem with this lesson
Kept only in this browser. Your Learn progress