Your Next Project Will Run on PHP
Mention PHP on a developer forum, in conversation, or while shopping at the town market, and the reaction is usually the same:
“PHP? Isn’t that dead? Only WordPress and Facebook still use it!”
It might even be a fair point. It’s just a surprising one to hear from your local fruit vendor. And since his business is selling fruit, not writing software, Ben (the fruit vendor, try to keep up) isn’t exactly up to date on what’s been happening in the world of modern PHP.
If you’re like Ben, this article is for you. We’ll cover deployment and scaling, software architecture, along with all the wonderful things the PHP ecosystem has been cooking up over the last few years. By the end, Hypertext Preprocessor might just become your favorite language!
One of the first issues to come up when discussing PHP is deployment. For some, the language is synonymous with shared hosting, managed WordPress installs, and, dare I say it, manually FTPing files from your machine to your server.
Fortunately, this hasn’t been the case for a very long time. In any serious project, Git is obviously the standard, and LAMP is now fully optional. The youngest among us are probably wondering what LAMP even is: it stands for Linux, Apache, MySQL and PHP.
PHP isn’t a web server, it’s an interpreter that runs a script and exits, so it needs something in front of it to accept HTTP requests. For years that was Apache with mod_php, which bakes the interpreter into every Apache worker process. PHP-FPM (FastCGI Process Manager) breaks that coupling: PHP runs as its own pool of long-lived workers, and the web server (Apache or nginx) forwards only the dynamic requests to it. That split is what made the now-standard nginx + PHP-FPM stack possible.
A common pattern in modern projects is to bundle Nginx and PHP-FPM into a single image that serves your application. It works, but it still demands a fair bit of LAMP-era thinking: setting up permissions, forwarding traffic to PHP-FPM, writing the Nginx config, and so on. It can get tedious, but prebuilt images like serversideup take care of all that plumbing for you, letting you develop locally and ship to production without giving any of it much thought. Still, Node, for example, needs none of this for a simple web server: you write a single file, run it, and it handles requests directly, with nothing in front of it.
FrankenPHP is probably one of the most interesting PHP projects in years, and it originates from Kévin Dunglas, the creator of API Platform and a Symfony core team member. He built it on top of Caddy, a modern web server written in Go.
But why should we care about yet another web server? Because FrankenPHP erases most of the hassle mentioned in the previous section.
It bakes PHP right into Caddy, so the server and PHP become one. No plumbing between Nginx and PHP-FPM, no config to write, no extra moving parts. You point it at your app and it runs. And since Caddy is a single Go binary, your whole project can ship as one tidy executable, with HTTPS handled for you out of the box.
The cherry on the cake is worker mode. Normally PHP rebuilds your entire framework from scratch on every request, then throws it all away when it replies. Worker mode boots your app once and keeps it warm, reusing it across thousands of requests. This allows your app to get noticeably faster, and for PHP to behave like the long-running process in Node.
Since worker mode keeps the same process alive, anything you leave lying around, like a global variable (PHP loves those!), now remains saved between requests, and can bleed from one user to another. Tools like Laravel Octane reset some of that state between requests, but it’s now your responsibility to clean up what you store in memory.
As of 2026, FrankenPHP is officially supported by the PHP Foundation.
The major framework players in PHP are Laravel and Symfony. Both implement a classic MVC, monolithic architecture. For a long time it was standard to dismiss monoliths as antiquated spaghetti monsters and reach instead for microservices spread across several instances. But the monolith is making a comeback.
React was introduced to the world in 2013, and over the next couple of years it took the world by storm. Suddenly everything was an SPA with a separated API, including Ben’s landing page, with a couple of banana pictures and an auto-playing parallax header video. But once all the frontend developer jobs were handed out, people realised that not everything needs to be client rendered, and hydration became a heavily debated topic.
Before long, Vercel created Next, a server-first React framework. The way it works is simple: you load the data on the server, render the HTML, and hydrate it with JavaScript. It even had a {var} syntax. Sound familiar? It was the monolith rearing its majestic head again.
Look, I get it. Monoliths can get really big and unruly. Once a codebase grows large enough, it’s hard to even remember which part does what. But there are solutions short of slicing it up into separate services, sometimes written in different languages, stitched together with RabbitMQ. The most elegant one is to organize your monolith into modules. For the sake of keeping this article short and to the point, I won’t expend on it too much, but there are great resources online about it, such as those:
A modular monolith still ships as a single unit. The difference is internal: each module owns its part of the domain, exposes a narrow public interface, and talks to the others through plain function calls instead of the network. You get the clean boundaries of microservices, without the latency, the serialization, or the distributed debugging. And if a module ever genuinely needs to scale on its own, those same boundaries make it easy to turn it into a separate service later.
Modularity should also be provided by your framework of choice, and this is where Symfony shines. Rather than handing you everything up front, it ships as a set of decoupled components and lets you pull in only the bundles you actually want. You start from a bare kernel and add the ORM, the console, the mailer, or the security layer as needed. The result is a framework that stays exactly as small as your app allows.
There’s another benefit to the monolith, and it’s a surprising one. With the rise of “agentic” coding, having all your business logic live in a single repository is now an advantage. To understand your codebase, agents grep and sed their way around with their harness. And using a standard, opinionated framework means the code that ended up in the model’s training data looks a lot like the code you’ll be writing.
Since agents love reaching for bash to do everything, the comprehensive CLI that Symfony and Laravel ship is another blessing on your way to becoming a 1000x ninja computer rockstar, or whatever HR is putting in the job postings these days.
We’ve already brushed up against this with FrankenPHP’s worker mode, where your app boots once and stays alive across requests. Concurrency takes that idea further. Out of the box, PHP does one thing at a time: a request comes in, the script runs top to bottom, and the moment it waits on a database query or an HTTP call, it just sits there blocking. Node solved this with a single-threaded event loop that, instead of waiting, moves on to the next task and circles back when the I/O is ready. PHP can now do the same. ReactPHP brings a Node-style event loop entirely in userland, while Swoole goes further, shipping a C extension with coroutines and async I/O that let a single worker juggle thousands of connections at once. Neither is part of the language yet, but a True Async RFC is making its way through core, which would bring async straight into the language itself.
PHP is gradually typed: you can add type declarations incrementally, mixing typed and untyped code as you go. What sets it apart is that PHP enforces those types at runtime rather than compile time. If a value turns out to be the wrong type while your application is running, PHP throws a TypeError at you.
This is significantly different from TypeScript, which checks types at compile time but has no way to verify those values at runtime. By the time the code actually runs, the types have been stripped away and all that’s left is plain old JavaScript. For runtime validation, you have to reach for a separate library, usually Zod.
PHP has its own tooling around typing, for things that aren’t supported by the language itself (like generics, although a proposal is on the way). PHPStan is a static analysis tool: it scans your whole codebase and flags type errors. You can run it locally and in CI.
Here’s an example of what it might look like:
/** * @param array{name: string, roles: list<string>} $user */function isAdmin(array $user): bool{ return in_array('admin', $user['role']);}PHP’s native type system only knows that $user is an array, which tells you almost nothing. The PHPDoc annotation goes further and describes the exact shape: a name string and a roles list of strings. With that, PHPStan can see the bug and warn you about it.
PHPStan’s strictness is configured through levels, from 0 to 10. The lower levels catch the blatant mistakes, like undefined variables and calls to methods that don’t exist, while the higher ones demand fuller type coverage, such as nullability and mixed values. Shape-aware checks like the one above only kick in around level 6 and up. The usual approach is to adopt PHPStan at a low level on an existing codebase, then increase it as you add annotations.
Formatting is handled by PHP CS Fixer, which rewrites your code to match a consistent style, usually one of the community PSR standards like PSR-12. And that’s only a glimpse of the ecosystem: Rector, Psalm, PHP_CodeSniffer, etc.
If I haven’t yet convinced you to give PHP a try, I hope I’ve at least piqued your interest. I already got Ben to ship his first Laravel application last week. If all else fails, I just want you to know that we have tech influencers with goofy thumbnails too, just like JS does!