PHP 4.0 shipped on 22 May 2000, two years after version 3, and the difference you can measure sits in the execution engine: the new Zend Engine, written by Zeev Suraski and Andi Gutmans, separates the compilation of a script from its execution. In PHP 3 the two phases were tangled in the same parse tree; now a script is first translated into intermediate opcode and then run against that opcode. For anyone writing applications larger than a welcome page, this separation is the first thing to understand.
Context
I started using PHP 3 to generate pages from SQL queries, and the limit showed on two fronts: performance when a template carried many loops, and the lack of any state handling that did not go through manual cookie hacks. PHP 3 was enough for a few lines of script inside an HTML document, but it held up less well once the logic grew and the file turned into a mixture of markup and code that was hard to follow.
The project’s documentation describes PHP as a language meant for the web and embeddable directly in HTML, and that is also its main architectural trait: not a standalone application server, but an interpreter that lives inside a web server’s request-response cycle.
Architecture: what the engine does on each request
The execution model is tied closely to HTTP. For every incoming request the interpreter:
- loads the requested
.phpfile; - compiles it into opcode (with the new Zend Engine this is a distinct phase);
- runs the opcode and produces the output;
- sends the result to the client and frees the allocated memory.
At step 4 the process state is wiped. Nothing accumulated during the request survives the request itself. I call this a shared-nothing model because two consecutive requests share no variables, objects, or connections unless an explicit persistence mechanism is used.
With the mod_php module for Apache, the interpreter runs inside the web server process, not as a separate CGI process launched on every request. The difference is tangible: traditional CGI forks a fresh interpreter for each request, while mod_php keeps the interpreter already loaded inside Apache. On a site with real traffic, sparing that fork is the item that weighs most on response time.
The critical point: repeated compilation
The Zend Engine’s split between parsing and execution is a genuine gain, but it brings an immediate problem with it. The opcode produced at step 2 is discarded at step 4 along with the rest of the memory. The next request to the same file recompiles the script from scratch, even when the source has not changed.
For a small file the cost is negligible. For an application with dozens of included files, compilation time is paid on every request and becomes a measurable fraction of latency. The split between opcode and execution opens the door to an opcode cache — keeping the product of step 2 and reusing it until the source changes — but the standard PHP 4.0 distribution does not include such a cache. It is a road the new engine makes passable, not a feature that arrives by installing the package.
State handling
PHP 4 brings session handling into the language core, where PHP 3 required an external library. A session links an identifier (in a cookie or in the query string) to a set of server-side data, and reconstructs that data on each request from the same client. It is the intended way to give continuity to a protocol, HTTP, that has none: every request arrives with no memory of the previous one.
Alongside sessions, version 4 exposes output buffering: a script’s output can be held in a buffer instead of leaving immediately for the client. It serves a practical case that comes up often — changing HTTP headers after some body has already been generated, which without a buffer is impossible, since in HTTP the headers precede the body and once sent cannot be withdrawn.
The stack and its parts
PHP occupies the application layer of a combination that runs entirely on software with available source: Linux as the operating system, Apache as the web server, a relational database such as MySQL or PostgreSQL, PHP as the language. Version 4’s database extensions cover MySQL, PostgreSQL, Oracle, and generic access through ODBC, so the choice of DBMS is not tied to a single product.
The operational benefit of this combination is reproducibility: each piece can be downloaded, compiled, and configured without usage licences, and that lowers the threshold for anyone who wants to understand how each layer works by reading its source.
Limits
Three things stay uncovered and are worth keeping in mind before building on top of them.
The first is the opcode cache missing from the standard package, already noted: the separated engine’s gain stays partly theoretical as long as every request recompiles.
The second is the sharing of expensive resources. The shared-nothing model simplifies reasoning about concurrent requests, but it also means each request opens and closes its own database connection, unless persistent connections are used — and those in turn must be sized carefully against the number of Apache processes, since each process can hold its own connection open and the total quickly saturates the SQL server’s limits.
The third is the language itself: the object in PHP 4 exists, but it is little more than an array with associated functions, with no real encapsulation and no distinction between public and private members. For a library shared across several projects this weighs, and it is worth knowing before, not after, choosing an object-based structure.
How much of all this will be filled by the third-party extensions already in circulation, and how much will instead need a new version of the engine, remains to be seen.
https://www.php.net/ https://www.php.net/manual/en/history.php.php https://www.php.net/manual/en/getting-started.php https://httpd.apache.org/ https://www.noze.it/en/insights/php4-open-source/
Cover image: Zeev Suraski, one of PHP’s principal authors, speaking at a 2005 conference — photo by Christophe Gesché, CC BY-SA 2.0 — https://commons.wikimedia.org/wiki/File:Zeev_Suraski_2005.jpg