For a long time, PHP had a very clear operating model.
A request comes in.
PHP starts.
The application runs.
A response goes out.
The process ends.
That model shaped a lot of how PHP applications were designed.
State could live in convenient places because the process was short-lived.
Global variables were less dangerous than they would be in a long-running process.
Memory leaks were often masked by process termination.
Cleanup was less visible because the runtime effectively cleaned everything up for you.
That model worked very well.
But PHP is evolving.
And the way we design PHP applications needs to evolve with it.
PHP is no longer only a request-per-process language
The traditional PHP-FPM model is still valid and useful.
But it is no longer the only serious way to run PHP.
Today, PHP can also run in long-lived workers, application servers, queue consumers, schedulers, event-driven processes, and persistent runtimes.
That changes something fundamental.
The application may now live for:
1 request
100 requests
10,000 requests
or hours of continuous workThat means old assumptions become more important.
What happens to request-specific state after the request finishes?
What happens to the current user?
The current tenant?
The database transaction?
Listeners?
Caches?
Telemetry context?
Temporary services?
A process that does not terminate forces us to answer questions that the traditional runtime often answered for us.
Performance is only part of the story
Persistent PHP is often discussed as a performance topic.
And performance does matter.
Booting the application once and reusing it can reduce repeated initialization work.
But I think the more interesting question is not:
“How much faster can PHP become?”
It is:
“What architectural assumptions change when the process survives?”
That is a much bigger question.
A persistent process can expose bugs that were previously hidden.
For example:
class UserContext
{
public static ?int $currentUserId = null;
}In a traditional short-lived request, this may appear harmless.
In a reused process, the next request may inherit stale state if cleanup is incomplete.
Now the problem is not performance.
It is isolation.
Dependency injection becomes more serious
Dependency injection is usually introduced as a way to improve testability and reduce coupling.
That is true.
But in long-running applications, it also becomes a lifetime problem.
Suppose an application-scoped service depends on a request-scoped service.
Conceptually:
Application Service
↓
Current UserIf the application service lives for hours while the current user should live for one request, the lifetime relationship is wrong.
The container should not simply allow that because the types happen to match.
This is where service lifetimes start to matter much more:
Application
Execution
TransientApplication services can live across many executions.
Execution services belong to one request, job, or command.
Transient services are created when needed.
The relationship between them becomes part of application safety.
Cleanup becomes part of correctness
In short-lived PHP, process termination often acts as cleanup.
In a persistent runtime, cleanup needs to become explicit.
Imagine this flow:
Request starts
↓
User state created
↓
Transaction opened
↓
Telemetry context created
↓
Handler runs
↓
Cleanup beginsIf cleanup succeeds, the process may be safe to reuse.
But what if cleanup fails?
That question does not get enough attention.
A framework should not simply assume:
“The request is over, continue.”
If the state of the process is uncertain, reusing that process can be dangerous.
Sometimes the correct answer is:
Do not reuse it.
That is a runtime safety decision, not just an error-handling decision.
Observability becomes more valuable too
Long-running systems are harder to understand when something goes wrong.
You may need to know:
which execution created the state,
which service failed to reset,
whether cleanup completed,
whether the process was reused,
what happened before a failure,
whether tenant or user context leaked.
Logs alone may not always be enough.
This is one reason I think observability needs to be designed into modern application architecture rather than added after problems appear.
Tracing, metrics, execution context, structured events, and failure evidence become more useful as runtime complexity increases.
PHP is also becoming more attractive for different workloads
The ecosystem is gradually making PHP viable for workloads that were traditionally pushed immediately toward other languages.
That does not mean PHP should replace everything.
Go, Rust, Java, and other languages remain excellent choices depending on the problem.
But PHP applications no longer need to assume:
“If this runs continuously, it must be rewritten in another language.”
Sometimes the correct answer may still be another language.
But it should be an architectural decision, not an automatic one.
If a PHP application can safely handle a workload with acceptable performance and operational behavior, there is value in keeping the system simpler.
Framework design needs to catch up
This is the part that interests me most.
A framework designed only around short-lived requests can still run in a persistent environment.
But that does not mean it was designed for one.
I think modern PHP frameworks increasingly need to think about:
explicit service lifetimes,
execution isolation,
deterministic cleanup,
stale-state prevention,
runtime reuse decisions,
observability boundaries,
worker safety,
concurrency assumptions,
graceful failure.
These concerns should not be hidden inside runtime adapters.
They should influence the framework architecture itself.
This is influencing EvolvePHP
A lot of the EvolvePHP 2 architecture has been shaped by this idea.
The runtime model is not limited to HTTP requests.
An execution might be:
HTTP request
Queue message
Scheduled job
CLI command
Worker taskEach execution gets its own scope.
Execution-specific state should not silently become application-global state.
Cleanup is explicit.
Cleanup failures remain visible.
And if execution cleanup leaves the process in an uncertain condition, the runtime can decide that the process should not be reused.
My goal is not to make PHP look like Java or Go.
I acknowledge that PHP itself is changing.
And frameworks should evolve with it.
PHP’s future is not just faster PHP
When people talk about the future of PHP, the conversation often focuses on syntax, JIT, benchmarks, or framework performance.
Those things matter.
But I think the deeper evolution is architectural.
PHP is moving into environments where processes live longer, workloads are broader, infrastructure is more distributed, and operational expectations are higher.
That means application design has to become more deliberate.
The old request-per-process model hid a lot of complexity.
Persistent runtimes expose it.
And that is not necessarily a bad thing.
It gives us a chance to build systems with clearer boundaries, stronger isolation, better observability, and more predictable behavior.
PHP is evolving.
The interesting question now is whether our application architecture evolves with it.

No comments:
Post a Comment