Saturday, 10 October 2026

Why Horizontal Scaling Exposes Bad State Management

Why Horizontal Scaling Exposes Bad State Management

A PHP application can run perfectly well on one server for years.

Then traffic grows.

You add a second application instance behind a load balancer.

And suddenly strange things start happening.

Users get logged out.

Uploads disappear.

Background jobs cannot find temporary files.

One request sees state created by another server.

Thursday, 8 October 2026

Evolve Doctor: What Should You Check Before Modernizing a PHP Application?

Evolve Doctor: What Should You Check Before Modernizing a PHP Application?


In the previous EvolvePHP post, I wrote about Evolve Audit.

Audit approaches an existing PHP application from the outside.

It asks:

“What can we learn about this codebase without running it?”

That is useful, but static evidence only tells part of the story.

At some point, modernization has to deal with the environment in which the application actually runs.

Which PHP version is active?

Are required extensions available?

Are important paths writable?

Are required environment variables present?

Do the assumptions in the project match the machine running it?

That is where I see Evolve Doctor fitting.

Wednesday, 7 October 2026

Coding Principles That Make PHP Applications Easier to Scale

Coding Principles That Make PHP Applications Easier to Scale

When people talk about scaling a PHP application, the discussion usually moves quickly toward infrastructure.

Redis.

Queues.

More servers.

Read replicas.

Containers.

Load balancers.

But infrastructure is only one part of the story.

A codebase that is difficult to reason about at small scale usually becomes more expensive to operate at large scale.

That is why I think scalability starts with coding principles long before it becomes an infrastructure problem.

Monday, 5 October 2026

Build for Change, Not for Distribution

Build for change

A lot of architecture discussions eventually become arguments about deployment models.

Monolith or microservices?

One database or many?

Synchronous or event-driven?

PHP or Go?

Containers or serverless?

Those are useful questions.

But I think they often arrive too early.

The Database Is Often the Hardest Part of Service Extraction

The Database Is Often the Hardest Part of Service Extraction

When people talk about extracting a service from a monolith, the conversation often focuses on code.

Move the controller.

Move the business logic.

Create an API.

Deploy it separately.

Done.

Except the difficult part is usually not the code.

It is the data.

You can move code in a day. Untangling data ownership can take months.

That is why I think service extraction should begin with the database question much earlier than most teams expect.

Saturday, 3 October 2026

Evolve Audit: What Can You Learn About a PHP Application Without Running It?

Evolve Audit: What Can You Learn About a PHP Application Without Running It?

Before modernizing an old PHP application, there is a question I think we should ask much earlier than:

“Which framework should we migrate to?”

The better first question is:

“What exactly are we dealing with?”

A mature PHP system can contain years of assumptions that are not obvious from looking at a few controllers.

Friday, 2 October 2026

Scalability Starts in the Code Before It Reaches the Infrastructure

Scalability Starts in the Code Before It Reaches the Infrastructure

When developers talk about scalability, the conversation often jumps quickly to infrastructure.

Load balancers.

More servers.

Redis.

Read replicas.

Kubernetes.

Queues.

CDNs.

Those things matter.

Wednesday, 30 September 2026

The Hidden Cost of Global State in PHP

The Hidden Cost of Global State in PHP

Global state is convenient.

That is why it survives.

A static singleton is easy to reach.

A global helper saves a few lines.

A shared container gives you access to almost anything.

Observability Is Not Logging

Observability Is Not Logging

A lot of systems say they have observability because they have logs.

They do not.

Logs are useful, sometimes essential, but they are only one part of understanding what a system is actually doing.

A system can produce thousands of log lines every minute and still be nearly impossible to diagnose when something goes wrong.

Thursday, 24 September 2026

Why Frameworks Should Help You Leave Them

Why Frameworks Should Help You Leave Them

A framework is supposed to help you build faster.

Routing, dependency injection, database access, queues, validation, authentication, caching, testing support—good frameworks remove huge amounts of repetitive work.

But there is another question I think framework design should take seriously:

What happens when the application eventually needs to move beyond the framework?

That might sound strange.

Wednesday, 23 September 2026

Can a PHP Application Be Designed for 10–20 Years of Change?

Can a PHP Application Be Designed for 10–20 Years of Change?

Most applications are not designed with a ten-year lifespan in mind.

They are built to solve the immediate problem.

Ship the product.

Get users.

Add features.

Then, somewhere along the way, the application becomes important enough that replacing it is no longer simple.

When Should a Modular Monolith Become Microservices?


A modular monolith can take you surprisingly far.

You can have clear business boundaries, independent modules, explicit contracts, good tests, background workers, queues, caching, horizontal scaling and strong deployment automation without splitting the application into separate services.

So at what point should one of those modules actually become a microservice?

Tuesday, 15 September 2026

EvolvePHP 2 Reaches Its First Alpha — The Framework Is Becoming Real

EvolvePHP 2 Reaches Its First Alpha — The Framework Is Becoming Real

This is a milestone I have been looking forward to for a while.

EvolvePHP 2.0.0-alpha.1 is officially out.

Until now, most of what I have written about EvolvePHP has focused on architecture: modular applications, execution isolation, long-running PHP, modernization, Bridge, service lifetimes and designing software for change.

With this release, those ideas are no longer only plans.

There is now a tagged, testable EvolvePHP 2 Alpha.

Microservices Are Not the Goal — Architectural Optionality Is

Microservices Are Not the Goal — Architectural Optionality Is

Microservices are often treated like the destination of a successful application.

Start with a monolith.

Grow.

Split into services.

Become “modern.”

I think that framing is backwards.

Microservices are not the goal.

Thursday, 10 September 2026

Long-Running PHP with EvolvePHP: Designing for Safe Reuse

Long-Running PHP with EvolvePHP: Designing for Safe Reuse

Long-running PHP is usually introduced as a performance story.

Boot the application once.

Keep the process alive.

Handle many requests or jobs.

Avoid repeating expensive initialization.

That can absolutely improve performance.

Wednesday, 9 September 2026

Evolving PHP: The Language Is Changing, and So Is the Way We Build With It

 

Evolving PHP: The Language Is Changing, and So Is the Way We Build With It  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 work  That 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.

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.

Tuesday, 8 September 2026

Migrating Legacy PHP Applications to Modern Frameworks: What Actually Changes?

Migrating Legacy PHP Applications to Modern Frameworks: What Actually Changes?

Migrating a legacy PHP application to a modern framework can look deceptively simple from the outside.

You have an old application.

You choose Laravel, Symfony, or another modern framework.

You move the routes, controllers, models, and views.

Done.

Except that is rarely what really happens.

The difficult part of framework migration is not translating syntax.

Monday, 7 September 2026

Modernizing Legacy PHP Applications Using EvolvePHP

Modernizing Legacy PHP Applications Using EvolvePHP

Modernizing an old PHP application sounds straightforward until you actually open the codebase.

You may find an old framework, unsupported PHP version, direct SQL everywhere, global state, fragile authentication, outdated packages, and years of business logic spread across controllers, cron jobs, helpers, templates and database procedures.

At that point, the usual instinct is:

“We need to move this to something modern.”

But that still leaves the hardest question unanswered:

How do we modernize without breaking the business that already depends on it?

Friday, 4 September 2026

Modernizing Legacy PHP Applications: Pros and Cons

Modernizing Legacy PHP Applications: Pros and Cons

Modernizing a legacy PHP application usually sounds like an obvious win.

Newer PHP version.

Supported dependencies.

Better architecture.

Cleaner code.

Improved security.

Easier deployment.

All of that sounds good.

But modernization is not free.

Every improvement comes with cost, risk, and disruption.

Modernizing Legacy Applications in PHP: Considerations and Approaches

 

Modernizing Legacy Applications in PHP: Considerations and Approaches

Legacy PHP applications are everywhere.

Some are ten or fifteen years old. Some are running on unsupported PHP versions. Some use frameworks that are no longer maintained. Others were built without a framework at all.

And many of them are still doing real work every day.

That creates an uncomfortable situation.

The application needs to change, but changing it may be risky.

Thursday, 3 September 2026

Secure by Default Is Not Enough — Frameworks Need Security Evidence

Secure by Default Is Not Enough — Frameworks Need Security Evidence

“Secure by default” sounds reassuring.

It is also something almost every serious framework wants to say.

Use parameterized queries. Set sensible cookie defaults. Escape output. Protect forms from CSRF. Make unsafe configuration harder.

All of that matters.

Saturday, 29 August 2026

Dependency Injection Is Easy Until the Process Lives Forever

Dependency Injection Is Easy Until the Process Lives Forever

Dependency injection feels simple when the application process is short-lived.

A request comes in.

The framework resolves a few services.

The request finishes.

Then everything disappears.

Under that model, a lot of lifetime mistakes are easy to miss.

Why “Just Rewrite It” Is Usually Bad Modernization Advice

Why “Just Rewrite It” Is Usually Bad Modernization Advice

There is a sentence developers say very easily when they are looking at an old codebase:

“We should just rewrite it.”

I understand the temptation.

You open a project and find an old PHP version, a framework that is no longer supported, classes with thousands of lines, duplicated business logic, direct SQL everywhere, globals, static state, and dependencies nobody wants to touch.

Starting again sounds cleaner.

Thursday, 27 August 2026

The Modular Monolith Is Still Underrated

The Modular Monolith Is Still Underrated

For a while, microservices became almost synonymous with “serious architecture.”

If an application was expected to grow, the assumption seemed to be that it should eventually become a collection of independently deployed services.

I understand why.

Part 2: What EvolvePHP Can Learn From Spring Without Becoming Spring for PHP

Part 2: What EvolvePHP Can Learn From Spring Without Becoming Spring for PHP

In the first part, I looked at why Java and Spring became trusted in enterprise environments.

The answer was not simply performance.

It was predictability.

Architecture.

Operations.

Security.

Lifecycle discipline.

Long-term support.

So what should a modern PHP framework actually learn from that?

Not everything.

And certainly not by trying to rebuild Spring in PHP.

Part 1: Can PHP Be an Enterprise Platform? What Java and Spring Got Right

Part 1: Can PHP Be an Enterprise Platform? What Java and Spring Got Right

PHP has powered a huge part of the web for years.

It is easy to deploy, widely understood, relatively inexpensive to run, and supported by mature frameworks such as Laravel and Symfony.

But once the conversation moves into banking, insurance, government systems, large enterprise platforms, or software expected to survive for ten or twenty years, another stack appears very quickly:

Java and Spring.

That raises a more useful question than “Is Java better than PHP?”

Friday, 21 August 2026

Why Persistent PHP Workers Need Execution Isolation

 


For most of PHP's history, developers have had a very useful safety net.

A request comes in.

PHP handles it.

The response is returned.

Then the request-specific state effectively disappears with the process lifecycle.

Conceptually:

Request starts
    ↓
Application runs
    ↓
Response returned
    ↓
Request state disappears

That model has shaped the way PHP applications are written for years.

Modernize PHP Without Rewriting Everything — Evolve Bridge

 


There is a kind of advice developers give very easily when they are not responsible for the consequences.

“Just rewrite it.”

I have heard versions of that advice many times.

An application is old.

The framework is outdated.

The codebase has grown messy.

Dependencies are difficult to upgrade.

Nobody fully understands some parts of the system anymore.

So the clean technical answer seems obvious:

Start again.

Sometimes that is the right answer.

But in real businesses, it is often the most dangerous one.

That problem is one of the reasons I am building Evolve Bridge.

Old software is not automatically bad software

A business application can be badly structured and still be extremely valuable.

It may have been running for ten years.

It may process customer registrations every day.

It may generate invoices.

It may connect to payment providers.

It may manage stock, claims, applications, subscriptions or government records.

It may contain thousands of small business rules that were added gradually because real users discovered real edge cases.

Those rules may not be documented anywhere else.

They exist in the code.

So when somebody says:

“We can rebuild this in six months.”

what they often mean is:

“We can rebuild the parts of the system we currently understand in six months.”

Those are not the same thing.

Rewrites hide risk

Suppose a business has this application:

Legacy PHP Application
├── Authentication
├── Customers
├── Billing
├── Orders
├── Reporting
├── Notifications
├── Admin
└── Third-party integrations

Maybe it was written using an older version of Laravel.

Maybe CodeIgniter.

Maybe Symfony.

Maybe CakePHP or Yii.

Maybe there is no framework at all.

After years of development, the team decides the architecture is becoming difficult to maintain.

A rewrite begins.

Now the company has two systems:

Existing Application
        +
New Application

The old one is still serving customers.

The new one is trying to catch up.

But the business does not stop changing while the rewrite happens.

A new tax rule arrives.

A payment provider changes its API.

Management wants a new approval workflow.

A customer discovers an important bug.

I have experience this in my former employment while they were adding new features to their existing Laravel application and rewrite the existing application on Node.js.

A regulator asks for another report.

Where do those changes go?

Usually both systems begin changing at the same time.

That is when the rewrite becomes more difficult.

The finish line keeps moving.

I think modernization should be smaller than that

This is the problem Evolve Bridge is intended to address.

Instead of beginning with:

“How do we replace this application?”

start with:

“What capability actually needs to change?”

That creates a very different modernization strategy.

Imagine the legacy application's reporting system has become painful.

Maybe generating reports takes too long.

Maybe reporting code is tightly coupled to everything else.

Maybe the business wants a new analytics team to work on it independently.

Instead of rebuilding the whole application, we could create a boundary:

Existing PHP Application
├── Authentication
├── Customers
├── Billing
├── Orders
├── Notifications
└── Admin

           |
           |
      Evolve Bridge
           |
           v

     Reporting Module

The old application continues working.

Only Reporting moves.

If the migration succeeds, perhaps another capability moves later.

If it does not make sense to move anything else, we stop.

That is perfectly acceptable.

Modernization should not become a religion.

Evolve Bridge is not a code converter

This distinction is important.

The goal is not:

Laravel code
    ↓
magic converter
    ↓
EvolvePHP code

I don't think serious modernization works that way.

Frameworks have different lifecycle assumptions.

Applications make different architectural decisions.

Business logic becomes mixed with framework behavior.

Automated conversion may help with mechanical work, but it cannot safely understand every architectural decision in a mature application.

So Evolve Bridge is being designed as an integration boundary, not a rewrite engine.

The job of the Bridge is to help two systems coexist deliberately.

For example:

Laravel
   |
   | delegated operation
   v
Evolve Bridge
   |
   v
Evolve Module

or:

Legacy PHP
     |
     | HTTP
     v
Evolve Service

or eventually:

Symfony Application
       |
       | Queue/Event
       v
  Evolve Capability

The integration mechanism can differ.

The principle stays the same.

One capability at a time.

Sometimes the systems can live in the same process

For compatible modern PHP applications, one possible Bridge model is embedded integration.

Conceptually:

Laravel / Symfony
       |
       v
  Evolve Bridge
       |
       v
  Evolve Module

Both systems run inside the same PHP process.

The host framework still owns the main application.

It may continue owning:

  • routing,

  • sessions,

  • existing authentication,

  • the top-level error lifecycle,

  • existing controllers,

  • existing business capabilities.

EvolvePHP only owns the part delegated to it.

That matters because I do not want integrating EvolvePHP to mean secretly placing another framework in control of the entire application.

Ownership has to be clear.

Other applications need process separation

Same-process integration is not always safe or possible.

Imagine an application running:

PHP 7.x
old framework
old Composer dependencies

while EvolvePHP 2 requires a modern PHP runtime and modern dependencies.

Trying to force those dependency trees into the same process would be a bad idea.

In that case the better architecture may be:

Legacy Application
       |
       | HTTP / Queue / Events
       v
 EvolvePHP Service

Now each side can have its own:

  • PHP version,

  • dependencies,

  • release cycle,

  • process lifecycle,

  • deployment strategy.

That is a stronger isolation boundary.

It also introduces distributed-system concerns such as latency, timeouts and retries.

Those concerns should not be hidden.

Remote integration is more expensive than an in-process call.

But sometimes that cost is exactly what allows an old system to keep running while new capabilities move forward.

One capability needs one owner

There is another rule I consider important.

During migration, it is easy to create situations where both systems think they own the same thing.

That becomes dangerous.

For example:

Legacy Billing
      +
New Billing

Which one is responsible for the invoice?

Which one owns the database record?

Which one applies refunds?

What happens if one succeeds and the other fails?

Modernization can accidentally create more complexity than it removes if ownership is unclear.

So the Bridge direction assumes something much simpler:

At any point in the migration, a capability or business aggregate should have one authoritative owner.

That owner can change during migration.

But the transition should be explicit.

Database ownership is one of the hardest parts

Moving controllers is easy.

Moving ownership of data is much harder.

Imagine an Orders module that currently writes to twenty legacy tables.

There are several modernization approaches.

You might initially allow the new module to read existing data through a carefully defined adapter.

Later you might introduce a new model.

Eventually the source of truth might move.

The important thing is that this transition should be planned rather than accidental.

Conceptually:

Stage 1
Legacy system owns data
Evolve reads through adapter

then perhaps:

Stage 2
Legacy owns old records
New operations delegated to Evolve

and eventually:

Stage 3
Evolve owns capability and data
Legacy becomes consumer

There is no single migration strategy that fits every application.

Evolve Bridge should provide boundaries and tooling.

It should not pretend difficult data migration questions disappear.

Authentication has the same problem

A legacy application may already know who the user is.

EvolvePHP should not necessarily require the user to authenticate twice simply because one capability has moved.

Instead, the host may translate the identity into a trusted integration contract.

But there is an important difference between:

"This host says the user is Josiah"

and:

"Josiah is allowed to perform this operation"

The first is identity.

The second is authorization.

EvolvePHP should still authorize operations it owns.

That means Bridge integration needs explicit trust boundaries.

Authentication data should not simply be copied between frameworks and assumed to be safe.

Remote writes are particularly dangerous

One area where I do not want Evolve Bridge to hide complexity is state-changing remote operations.

Imagine:

Legacy Application
      |
      | POST /payment
      v
Evolve Service

The request times out.

Did the payment happen?

A timeout does not mean:

“Nothing happened.”

The remote side may have completed the operation and the response was simply lost.

If the legacy application retries blindly, it could perform the operation twice.

This is why remote modernization eventually needs patterns such as:

  • idempotency keys,

  • operation identifiers,

  • reconciliation,

  • explicit failure states,

  • safe retry policies.

This kind of behavior is especially important in financial and enterprise systems.

Framework integration should not give developers false confidence around distributed failure.

Rollback matters as much as migration

Developers naturally think about the happy direction:

Legacy
   ↓
Modern

But real migrations sometimes fail.

A new capability may have performance problems.

The integration may behave differently under production traffic.

An important edge case may be missing.

The company may need to temporarily return traffic to the old implementation.

So the modernization process should also ask:

How do we go back safely?

That could involve:

route ownership
feature flags
deployment version
data compatibility
migration checkpoint
rollback validation

A migration without a rollback story is not a controlled migration.

It is a bet.

Bridge should be useful before the migration begins

The more I have thought about modernization, the more I have realized that the actual integration layer is only one part of the problem.

Before moving anything, teams need to understand what they have.

That is why the broader EvolvePHP modernization direction now includes tools such as:

Evolve Audit
     ↓
Evolve Doctor
     ↓
Adoption Plan
     ↓
Evolve Bridge
     ↓
Modernize

Evolve Audit is intended to help answer questions like:

  • What PHP version is this system using?

  • Which dependencies are abandoned?

  • Where is global or static state being used?

  • Where are the strongest coupling points?

  • Which areas look like natural capability boundaries?

  • What could prevent persistent-runtime adoption?

  • Which parts of the application are the best modernization candidates?

Doctor has a more operational role.

It should answer things like:

  • Is the environment correctly configured?

  • Are required extensions available?

  • Is this application safe for the runtime being proposed?

  • Are there configuration or lifecycle problems that would make the migration unsafe?

I think modernization starts with diagnosis.

Not with writing new code.

This also changes how EvolvePHP competes

EvolvePHP does not need to convince every framework developer to stop using their preferred framework.

If EvolvePHP only provides value after you move the whole system to EvolvePHP, adoption becomes extremely expensive.

I want the opposite.

The framework should be able to earn trust gradually.

This is important for enterprise systems

Enterprise applications rarely have the luxury of being replaced overnight.

A government platform may have hundreds of workflows.

A bank may have dozens of surrounding systems connected to its core platform.

An ERP may contain years of company-specific behavior.

A marketplace may have sellers, buyers, billing, messaging, search and logistics integrations.

The safest modernization strategy is often not:

old system → new system

It is closer to:

old system
    |
    +---- capability A
    |
    +---- capability B
    |
    +---- capability C

and then gradually:

old system
    |
    +---- capability A
    |
    +---- Evolve capability B
    |
    +---- capability C

and later perhaps:

old system
    |
    +---- Evolve capability A
    |
    +---- Evolve capability B
    |
    +---- capability C

There may never be a dramatic rewrite day.

The architecture simply evolves.

I think that is healthier.

Sometimes the correct decision is not to migrate

This is also something modernization tooling should be able to tell you.

Suppose Audit examines an old capability and you discover:

  • it works reliably,

  • it rarely changes,

  • it has no serious security problems,

  • there are good tests,

  • maintenance cost is low.

Why rewrite it?

Modernization should solve business and engineering problems.

Not satisfy architectural fashion.

A good modernization platform should sometimes say:

Leave this alone.

That is a feature, not a failure.

Evolve Bridge is really about reducing the cost of change

The idea behind EvolvePHP 2 has increasingly become less about:

“How do I build another PHP application?”

and more about:

“How do I make PHP applications easier to change over their lifetime?”

For greenfield systems, that means starting modular.

For existing systems, it means creating controlled paths to evolve.

Evolve Bridge sits between those two worlds.

It should allow something built ten years ago to cooperate with something being built today.

Not because the old architecture is perfect.

Not because the new architecture is automatically better.

But because replacing a working business system all at once is often unnecessary risk.

What success would look like

If Evolve Bridge works the way I want, a modernization conversation might eventually look like this:

We have a twelve-year-old PHP application.

Fine.

It cannot be rewritten right now.

Fine.

We want to move Billing first.

Let's analyze the boundary.

Authentication must remain in the old application.

Fine.

The old application is running an incompatible PHP version.

Use a remote boundary.

We need rollback.

Define ownership and rollback before cutover.

After Billing works, we may move Reporting.

Good.

We may never move Customer Management.

Also good.

That is very different from telling the company:

“Come back when you're ready to rebuild everything.”

Modernize without rewriting everything

I don't think the future of PHP depends only on helping developers create more new applications.

There are already enormous amounts of PHP software running businesses today.

A lot of it will still be running five or ten years from now.

The interesting question is what happens to those systems as technology changes around them.

New PHP versions.

New deployment environments.

Persistent workers.

Cloud infrastructure.

New security requirements.

New observability expectations.

New business requirements.

Those applications need somewhere to go.

Evolve Bridge is my attempt to create one possible path.

Not:

Rewrite everything.

But:

Understand what you have. Create a boundary. Move what needs to move. Keep what still works. Repeat only when it makes sense.

That is the modernization philosophy behind EvolvePHP.

Modernize PHP without rewriting everything.

Monday, 17 August 2026

Why Another PHP Framework When Laravel and Symfony Exist?


This is probably the most obvious question anyone can ask about EvolvePHP 2.

Why build another PHP framework?

Laravel already exists.

Symfony already exists.

Both are mature, actively developed, widely used, and capable of building serious applications.

Laravel gives developers an enormous amount out of the box: dependency injection, routing, queues, testing, authentication packages, Artisan, Octane, monitoring tools and a large ecosystem around the framework. Its current documentation describes it as a progressive framework that can grow from beginner projects to applications using dependency injection, queues, real-time events and other advanced capabilities.

Symfony approaches things differently. It provides both a full framework and a large collection of independent PHP components. Its Runtime component can decouple application bootstrapping from global state and support different runtime environments, while its wider component ecosystem covers HTTP, messaging, caching, configuration, validation, processes and much more.

So if the goal of EvolvePHP 2 was simply:

Build routing, controllers, dependency injection, database access and authentication.

Then I would probably tell myself to stop.

Those problems have already been solved.

And they have been solved very well.

I am not trying to build a Laravel replacement

This is an important distinction for me.

I don't want the motivation behind EvolvePHP to become:

Laravel does X, so EvolvePHP must have X.

Or:

Symfony has Y, so EvolvePHP needs a better Y.

That becomes an endless race that a small open-source framework is unlikely to win.

Laravel has years of development behind it, a huge ecosystem and a developer experience that has been refined over a long period.

Symfony has an extremely mature component architecture and is used directly and indirectly across a large part of the PHP ecosystem.

Trying to compete with either framework simply by having more features would not make much sense.

Instead, I started asking a different question:

What problems do I want EvolvePHP to care about more deeply?

That question changed the direction of the project.

Framework choice is rarely the biggest problem years later

When starting a new application, choosing a framework feels like one of the biggest technical decisions.

A few years later, that may no longer be the biggest problem.

The bigger problems become things like:

  • How do we upgrade this application safely?

  • Why is everything coupled together?

  • Can one part of the application scale separately?

  • Can we introduce a new architecture without rewriting everything?

  • Can we move to a persistent runtime safely?

  • What happens to request-specific state when the same worker handles another request?

  • How do we understand what is happening across the application?

  • How do we modernize an old system without stopping business for a year?

Those questions interest me more.

EvolvePHP 2 is being designed around the idea that software will change, and the framework should help the application survive that change.

That is the space I want EvolvePHP to explore.

A framework built around architectural evolution

A typical business application may begin very simply.

Imagine this:

Application
├── Users
├── Billing
├── Orders
├── Notifications
└── Reporting

There is nothing wrong with deploying this as one application.

In fact, for many projects, that is exactly what I would prefer.

I don't think every new application needs to start with microservices, message brokers and complicated infrastructure.

But I would like those five areas to have meaningful boundaries.

If Reporting becomes expensive three years later, maybe it should run separately.

If Notifications grows dramatically, perhaps it eventually belongs in a worker.

If Orders becomes a capability maintained by another team, perhaps it eventually becomes independently deployable.

The important question is:

How much of the original application do we have to destroy to make that change?

I want EvolvePHP to make the answer:

As little as reasonably possible.

That is why modularity is not just a folder structure in the EvolvePHP 2 architecture.

Modules are intended to represent actual application capabilities with explicit dependencies and lifecycle rules.

The application can remain a modular monolith for as long as that architecture continues to make sense.

Only when there is a real reason to distribute something should the deployment architecture become more complicated.

I don't want developers choosing microservices because they are afraid of the future

Sometimes teams over-engineer a new system because they don't trust themselves to change it later.

They think:

We might need independent scaling in three years, so let us build ten services now.

That can introduce problems long before the benefits appear.

Now there are network calls.

Service discovery.

More deployments.

Distributed tracing.

Retries.

Timeouts.

Partial failures.

Data ownership questions.

More infrastructure.

More operational complexity.

All for an application that may currently have a few hundred users.

I would rather have an architecture where developers can say:

We can keep this simple today because we have a reasonable path to change it tomorrow.

That is one of the long-term ideas behind EvolvePHP 2.

Start modular.

Stay together while staying together makes sense.

Extract selectively when there is evidence that separation is useful.

But new applications are only half the story

There is another problem that matters to me because I have seen it repeatedly in real software projects.

Existing systems.

Some business applications have been running for ten or fifteen years.

They may not be beautiful.

They may contain outdated dependencies.

They may have architecture decisions nobody would make today.

But they work.

Customers depend on them.

Employees depend on them.

Money moves through them.

And somewhere inside those applications are thousands of lines of business rules that took years to discover.

The usual technical answer is easy:

Rewrite it.

The business answer is much harder.

A rewrite means rebuilding not only the code developers understand, but also all the strange business behavior that nobody remembered to document.

Then while the rewrite is happening, the business does not stop changing.

New requirements continue arriving.

New integrations are needed.

Regulations change.

Customers continue reporting problems.

The old system keeps moving while the new system tries to catch it.

That can become dangerous.

What if modernization didn't require adoption on day one?

This is where EvolvePHP's direction becomes more interesting to me.

I want developers to eventually be able to get value from EvolvePHP without first deciding to rebuild their application using EvolvePHP.

That is a very different adoption model.

Imagine a Laravel application.

Or Symfony.

Or CakePHP.

Or Yii.

Or CodeIgniter.

Or even a completely custom PHP system built ten years ago.

Instead of saying:

Move the application to EvolvePHP.

the first question could be:

What part of this application actually needs to change?

That thinking led to the architecture behind Evolve Bridge.

Conceptually:

Existing Application
        |
        |
   Evolve Bridge
        |
        |
 New Capability

The existing system remains responsible for what it already owns.

A new capability can begin on the modern side.

Over time, additional capabilities can move if there is a reason to move them.

There is no rule saying the original framework must disappear.

In fact, if Laravel continues doing something well, there may be no reason to replace that part at all.

That is important.

Coexistence instead of framework wars

Framework discussions sometimes become strangely competitive.

Laravel versus Symfony.

Symfony versus something else.

PHP versus Python.

Monolith versus microservices.

I don't think software architecture works that cleanly.

Real systems are messy.

A company might have:

Laravel application
        +
old PHP reporting system
        +
WordPress customer portal
        +
Python data service
        +
Node.js notification service

That isn't unusual.

The question becomes how those systems can evolve without creating unnecessary risk.

So I want EvolvePHP to be comfortable existing beside other frameworks.

A Bridge adapter should not mean:

Laravel is bad. Replace it.

It should mean:

Here is a defined boundary where Laravel and EvolvePHP can cooperate.

Symfony itself demonstrates the value of interoperability through its component model. Many Symfony components can be installed independently instead of requiring developers to adopt the complete Symfony framework.

I think PHP benefits when tools cooperate rather than expecting every project to become an all-or-nothing framework decision.

Persistent runtimes changed one of my assumptions

Another area where I want EvolvePHP to think differently is runtime safety.

Traditional PHP gives developers something very convenient.

A request starts.

PHP runs.

The response finishes.

The request state disappears.

That lifecycle protects developers from many mistakes.

But long-running PHP runtimes change the situation.

Laravel has Octane, which keeps an application in memory and serves requests through application servers such as FrankenPHP, Swoole and RoadRunner. Laravel also provides scoped service bindings specifically for cases such as Octane requests and queue-worker jobs.

Symfony's Runtime component similarly exists to separate application bootstrapping from global state and allow applications to work with different runtime environments.

So persistent PHP itself is certainly not something unique to EvolvePHP.

The difference is that I want EvolvePHP's architecture to assume from the foundation that process reuse is dangerous unless isolation can be demonstrated.

That has led to the idea of an execution.

An execution could eventually be:

HTTP request
Queue message
Scheduled job
CLI command
Worker task

Each one gets isolated execution state.

When the execution finishes, cleanup must happen.

If cleanup succeeds, the process may be safe to reuse.

If cleanup fails and EvolvePHP cannot prove that state from the previous execution has been removed, then the framework should fail closed.

The process should be marked for quarantine rather than quietly accepting the next execution and hoping everything is fine.

For me, this is not primarily a performance feature.

It is an isolation feature.

Why does that matter?

Imagine a worker accidentally retains:

currentUser = Customer A

Then Customer B's request arrives.

Or imagine:

currentTenant = Company A

surviving into work being performed for Company B.

Now a small state-management mistake has become a security problem.

The framework cannot prevent every application bug.

But it can make certain dangerous patterns more difficult and give itself explicit cleanup responsibilities.

That is the type of framework behavior I want EvolvePHP 2 to take seriously.

Observability should not be something we remember later

There is another problem I have seen in applications.

Everything works until it doesn't.

Then somebody asks:

Why is this request slow?

And suddenly nobody really knows.

Was it the database?

An external API?

A specific module?

A queue?

A cache miss?

Authentication?

Some unexpected event listener?

The information might exist somewhere, but the application was never designed to expose it cleanly.

EvolvePHP is therefore being designed with an instrumentation boundary from the beginning.

The direction currently separates two ideas:

Evolve Insight for developer-focused local diagnostics.

Evolve Observe for production telemetry and OpenTelemetry integration.

I don't want Evolve Observe to become another Datadog or Grafana.

Those systems already exist.

The framework's responsibility should be to produce meaningful, vendor-neutral telemetry that developers can send to the tools they choose.

Again, the difference is not that Laravel or Symfony cannot be observed.

They absolutely can.

The difference is that I want observability to be part of EvolvePHP's architectural contracts rather than something we discover we need after the framework is already designed.

Another framework doesn't need another ORM to justify itself

This is probably one of the biggest changes in how I think about the project.

Years ago, if you asked me what made a PHP framework different, I might have compared:

  • routers,

  • ORM features,

  • templating,

  • helpers,

  • authentication,

  • session management.

Those things still matter.

Developers need a productive experience.

EvolvePHP eventually needs good routing, commands, testing utilities, database integrations and all the normal things expected from a modern framework.

But I don't think those should be its identity.

The identity should be closer to:

How safely can this application change over the next ten years?

Can I understand it?

Can I modularize it?

Can I upgrade it?

Can I run it differently?

Can I observe it?

Can I modernize only part of it?

Can I replace one capability without replacing everything?

Can I keep useful parts of an existing framework?

Can I discover when my runtime is unsafe?

Those are the questions I want EvolvePHP to answer.

There will still be cases where Laravel is the better choice

I want to be clear about this too.

If someone wants to build a standard SaaS application quickly, Laravel may be the obvious choice.

Its ecosystem is huge.

Its developer experience is excellent.

It has solutions for authentication, queues, billing, search, broadcasting, monitoring, testing, deployment and many other common requirements. The current Laravel documentation reflects how broad that ecosystem has become.

Likewise, if a project needs mature reusable PHP components, deep enterprise architecture and an established component ecosystem, Symfony is extremely difficult to ignore.

EvolvePHP does not become more credible by pretending those strengths don't exist.

In fact, I think acknowledging them makes the purpose of EvolvePHP clearer.

The goal isn't:

Choose EvolvePHP because Laravel and Symfony failed.

The goal is:

Choose EvolvePHP when the problems EvolvePHP prioritizes match the problems you expect your system to face.

So why another PHP framework?

Because I think there is still room to explore a framework where change itself is one of the main design constraints.

A framework where a new project can begin as a modular monolith without committing to permanent monolithic deployment.

A framework where existing applications can adopt new capabilities incrementally.

A framework that is comfortable cooperating with Laravel, Symfony and legacy PHP instead of demanding immediate replacement.

A framework where request, job and worker isolation are designed into the execution model.

A framework where failed cleanup means something.

A framework where observability is considered while the architecture is being created.

And eventually, a framework that can help developers answer:

What will break if I modernize this application?

That is enough reason for me to explore the idea.

It may not be the framework everyone needs.

It shouldn't try to be.

If EvolvePHP becomes useful to developers building long-lived modular applications, or to businesses trying to modernize systems they cannot afford to rewrite, then it has a reason to exist.

Laravel does not need to lose for EvolvePHP to succeed.

Symfony does not need to lose either.

PHP gets stronger when developers have more good ideas to choose from.

And EvolvePHP 2 is my attempt to contribute another one:

Build for change. Modernize without rewriting everything.

Thursday, 13 August 2026

EvolvePHP 2: A PHP Framework Built for Change

When I started rebuilding EvolvePHP, I knew one thing very early:

I did not want to build another PHP framework simply because I could.

PHP already has Laravel, Symfony, CakePHP, Yii and several other mature frameworks. They have large communities, years of development behind them, extensive documentation and ecosystems that EvolvePHP cannot realistically compete with by simply offering another router, ORM, service container or authentication system.

So the question I kept asking myself was:

Why should EvolvePHP 2 exist?

The answer I keep coming back to is change.

Software changes.

Businesses change.

Requirements change.

Teams change.

Infrastructure changes.

And sometimes the framework or architecture you chose five or ten years ago is no longer the architecture your application needs today.

That is where I want EvolvePHP 2 to focus.

Applications rarely remain what we originally designed

A lot of applications start small.

Maybe you are building a SaaS product with users, subscriptions, notifications and reports.

At the beginning, putting everything inside one application makes perfect sense.

You don't need twenty services.

You don't need Kubernetes.

You probably don't need a complicated event-driven architecture.

You need to ship.

But if the application succeeds, things start changing.

The reporting system becomes expensive to run.

Notifications begin processing millions of messages.

A certain part of the system needs to scale differently.

The business expands into another country.

Another development team takes ownership of one part of the application.

A third-party integration becomes critical.

Something that originally looked like this:

Application
├── Users
├── Billing
├── Orders
├── Notifications
└── Reports

may eventually need to become something different.

The problem is that many architectural decisions become very expensive to undo later.

EvolvePHP 2 is being designed around the idea that we should expect this change instead of pretending it will not happen.

Start with a modular monolith

I am not interested in encouraging developers to start every new application with microservices.

In many cases, I think that creates unnecessary complexity.

A new EvolvePHP application should be able to begin as a normal application deployed as one unit.

But internally, I want the application to encourage clear boundaries.

Instead of treating the whole application as one large collection of controllers, models and services, the system can be organised around capabilities:

Application
├── Identity
├── Billing
├── Orders
├── Notifications
└── Reporting

Each module should have a clear responsibility.

Its dependencies should be explicit.

Its contracts should be understandable.

The application can still be one deployable system.

That gives you the simplicity of a monolith without giving up every architectural boundary inside it.

Then, if the business reaches a point where Reporting needs to run separately, the goal is not to redesign the entire application just to make that possible.

The architecture should already give us somewhere to begin.

That is what I mean when I say EvolvePHP is being built for change.

I want deployment architecture to be able to evolve

One of the long-term goals behind the framework is that a module should not be unnecessarily tied to where it currently runs.

A module may begin embedded inside the application.

Later, that same business capability may need to run inside a worker.

Eventually it may need to become a separate service.

Conceptually:

Today

Application
├── Billing
├── Orders
└── Reporting

could become:

Later

Application
├── Billing
└── Orders

        |
        |
        v

Reporting Service

This does not mean moving a module remotely will magically require zero engineering work.

Distributed systems introduce networking, failure handling, latency, retries, idempotency, security and many other concerns.

Pretending otherwise would be misleading.

What I want EvolvePHP to do is reduce the amount of business architecture that must be thrown away when that transition happens.

The framework should help you create boundaries early enough that future change is possible.

Existing applications also need a path forward

The idea of building for change does not only apply to new applications.

There is another problem I care about just as much: existing PHP systems.

There are applications running today that were written many years ago and still make money for businesses.

Some are custom PHP applications.

Some use older versions of CodeIgniter, Laravel, Symfony, CakePHP, Yii or other frameworks.

Some were designed before containers, cloud deployment, persistent workers and modern observability were normal considerations.

The easy advice is:

Rewrite it.

The practical reality is very different.

A ten-year-old business application may contain years of business rules that nobody has documented properly.

It may have hundreds of database tables.

It may communicate with government systems, banks, payment providers or internal services.

Customers may depend on it every day.

A complete rewrite might take years, and the business still has to continue changing while the rewrite is happening.

So instead of making EvolvePHP 2 require an all-or-nothing migration, I am designing Evolve Bridge around incremental adoption.

The goal is to make something like this possible:

Existing Application
        |
        |
   Evolve Bridge
        |
        |
New Evolve Module

The existing application does not immediately disappear.

You modernise one capability.

Then another.

And another, if it makes sense.

Maybe the old application is eventually replaced completely.

Maybe it isn't.

The important part is that adopting EvolvePHP should not require the business to gamble everything on one massive rewrite.

Even EvolvePHP itself should not become a trap

There is another side to this.

If I say EvolvePHP helps developers escape architectural traps created by older systems, then EvolvePHP itself should not become another trap.

That means I have to think carefully about framework boundaries.

Application business logic should not need to know unnecessary details about framework internals.

Modules should not be able to depend on anything they want.

Plugins and application modules should not mean the same thing.

Package dependencies should remain directional.

Public contracts should be intentional.

These things may sound restrictive when you are building something quickly.

But years later, those boundaries are often what determine whether a system is easy or painful to change.

I learned some of this from EvolvePHP 1.

When you maintain software long enough, you start appreciating decisions that make tomorrow's work easier.

Runtime assumptions also change

PHP itself is changing.

Traditionally, PHP applications had a very convenient lifecycle:

Request starts
    ↓
Application runs
    ↓
Response returned
    ↓
Process state disappears

That model hides many mistakes.

If a developer accidentally stores request-specific information in static state, it often disappears at the end of the request anyway.

With persistent workers, that assumption changes.

The same process may handle another request.

Or another queue message.

Or another task.

State from one execution can accidentally survive into the next.

That becomes much more serious when the leaked information contains:

  • the current user,

  • tenant information,

  • database transaction state,

  • listeners,

  • locale,

  • authentication context,

  • logging context,

  • or other execution-specific data.

So EvolvePHP 2 does not treat persistent-worker safety as something that can simply be added later by putting the framework behind a fast server.

The framework is being designed around explicit service lifetimes and isolated executions.

At the foundational level, I currently think about services as:

Application
Execution
Transient

An application service can live for the application lifecycle.

An execution service belongs to one request, job, command or task.

A transient service can be created when needed.

More importantly, when an execution ends, the framework needs to be able to clean up that execution deterministically.

If cleanup fails and the framework cannot prove the process is safe for another execution, the correct response should not be:

Hopefully it is fine.

The process should be treated as unsafe for reuse.

This is the idea behind the quarantine model being developed in EvolvePHP 2.

HTTP is not the whole application

Another change in my thinking is that I don't want the entire framework architecture to assume that everything is an HTTP request.

Modern applications do much more than HTTP.

They process:

  • web requests,

  • queue messages,

  • scheduled jobs,

  • CLI commands,

  • background worker tasks.

So internally, EvolvePHP is developing the broader concept of an execution.

An HTTP request is an execution.

A queue message is an execution.

A scheduled job is an execution.

A command is an execution.

They have different inputs and outputs, but they share important lifecycle concerns:

Execution starts
    ↓
Context created
    ↓
Application work
    ↓
Cleanup
    ↓
Isolation verified
    ↓
Execution ends

This gives the framework a more consistent foundation for different runtime environments without pretending those environments are identical.

Observability should be part of the architecture

Another thing that becomes important as applications grow is understanding what they are actually doing.

When a request becomes slow, I want developers to be able to answer:

  • Which module handled it?

  • What services were involved?

  • How many database operations happened?

  • Which external calls were made?

  • Where was the time spent?

  • What happened during cleanup?

  • Did the execution end safely?

This is why EvolvePHP has two observability directions in its architecture.

Evolve Insight is intended for local and development diagnostics.

Evolve Observe is intended for production telemetry and OpenTelemetry integration.

They are different tools serving different environments, but both can eventually benefit from the same framework instrumentation.

Again, this is not about trying to rebuild Grafana, Datadog or an OpenTelemetry backend.

The framework should produce useful information and integrate with the tools that already exist.

Understanding change before making it

The modernization direction has also led to another idea I am increasingly interested in: EvolvePHP should eventually help developers understand an existing system before asking them to change it.

That is the thinking behind Evolve Audit and Evolve Doctor.

Audit is intended to answer questions such as:

What exactly am I dealing with?

It could eventually inspect areas like:

  • PHP version risk,

  • dependency health,

  • framework lifecycle,

  • global and static state,

  • application coupling,

  • possible module boundaries,

  • persistent-runtime risks,

  • modernization candidates.

Doctor has a different job:

Is this application correctly configured and safe for the environment I want to run it in?

Those tools are not the framework today; they are part of the direction being designed for later phases.

But they fit the same philosophy.

Before changing a system, understand it.

Before upgrading it, know what could break.

Before putting it inside a persistent runtime, determine whether its state is actually isolated.

This is why I say "built for change"

When I describe EvolvePHP 2 as a PHP framework built for change, I don't mean that every future architectural change will become automatic.

Software doesn't work that way.

There will still be difficult migrations.

There will still be bad architectural decisions.

There will still be systems that need major redesigns.

What I want to provide is a better starting position.

For a new application:

Start modular, without starting distributed.

For an existing application:

Modernise incrementally, without requiring a full rewrite.

For a growing application:

Extract capabilities when the business actually needs it.

For runtime changes:

Treat isolation and cleanup as architectural requirements.

For operations:

Make the system observable enough to understand what it is doing.

And for upgrades:

Reduce uncertainty before making the change.

That is the direction.

EvolvePHP 2 is still being built

I think it is important to say this clearly.

EvolvePHP 2 is under active development.

Some of what I have described here represents architecture that has already been defined and foundational work that is being implemented.

Other parts, particularly the broader modernization and adoption tooling, belong to later stages of the roadmap.

I would rather document the thinking openly than wait until everything is finished and pretend the final architecture appeared fully formed.

Some decisions may still improve as implementation provides evidence.

That is part of building software too.

EvolvePHP 1 evolved because I kept using it on real projects and learning from it.

EvolvePHP 2 is being approached more deliberately, but I still expect implementation, testing and real-world use to challenge some assumptions.

And when that happens, the framework should be willing to evolve.

After all, that is the entire point.

EvolvePHP 2 is not being built around the assumption that software stays the same.

It is being built around the reality that good software has to survive change.

Wednesday, 12 August 2026

Why I’m Rebuilding EvolvePHP: Lessons From a Framework I Built Years Ago

 

Back in early 2016, while working on several PHP projects, I didn’t realize I was building a framework.

At the time, I had already learned CodeIgniter and was proficient with WordPress and Joomla. Most of the applications I was working on were not simple websites; they were larger business and enterprise systems with recurring requirements such as user management, authentication, roles, access control, sessions, administrative interfaces, and other shared functionality.

As I moved from one project to another, I started noticing a pattern.

I was repeatedly rebuilding many of the same features.

User management.

Login and authentication.

ACL.

Roles and permissions.

Database access.

Routing.

Sessions.

Controllers.

Common utilities.

I wanted a better way to reuse those capabilities without copying an old application and removing everything I didn't need.

evolvephp1-evolvephp2

 

The idea started with modularity

CodeIgniter influenced how I thought about MVC and application structure, but Joomla had also exposed me to a different idea that I found very interesting: components and modular functionality.

I liked the idea of being able to build a feature independently and then plug it into another application when needed.

Instead of every new project starting like this:

New project
    ↓
Build authentication again
    ↓
Build roles again
    ↓
Build ACL again
    ↓
Build common infrastructure again

I wanted something closer to:

Application
    ├── Users
    ├── Authentication
    ├── ACL
    ├── Reporting
    └── Other reusable components

If another application needed one of those capabilities, I could reuse or adapt the component rather than starting from scratch.

So I began creating my own structure around the applications I was developing.

At that point, I wasn't really thinking:

"I am going to create a PHP framework."

I was simply trying to solve a problem I kept encountering in my daily development work.

Then I realized I had built a framework

After deploying one of the applications, I looked at the underlying code more carefully.

By then, I had already created many of the things we normally associate with a framework:

  • Routing

  • MVC structure

  • Session management

  • Database abstractions

  • Controllers and models

  • Reusable components

  • Configuration management

  • Authentication-related utilities

  • ACL and permission handling

  • Common application helpers

The application-specific code was sitting on top of a reusable foundation.

At that point it became obvious that what I had created was no longer just a collection of helper files.

It was the beginning of a framework.

That framework eventually became EvolvePHP.

EvolvePHP grew with the applications I built

I continued using the framework for other projects.

Whenever I encountered something missing, I added it.

Whenever I found myself repeating code between projects, I looked for a way to make that functionality reusable.

Whenever an application exposed a weakness in the framework, I improved the framework.

So EvolvePHP grew organically.

It wasn't designed in one sitting from a perfect architectural specification.

It evolved alongside real applications and real business requirements.

That experience was incredibly valuable.

Several applications I built with EvolvePHP are still running today, years after they were originally deployed.

For me, that matters more than whether the framework ever became widely adopted publicly.

It proved that the ideas behind it were useful enough to support real systems.

What EvolvePHP 1 taught me

Building EvolvePHP taught me far more than I expected when I started.

It forced me to understand PHP beyond simply using existing frameworks.

I had to think about questions such as:

  • How should requests enter an application?

  • How should routes be resolved?

  • How should controllers be instantiated?

  • How should reusable components communicate?

  • How should authentication state be handled?

  • How should permissions be represented?

  • How should application configuration work?

  • How should database access be structured?

  • How much responsibility should a framework take from the application?

  • Where should framework code end and business code begin?

It gave me practical experience with:

  • PHP internals and application structure

  • MVC architecture

  • Reusable software design

  • ACL and authorization systems

  • Framework development

  • System design

  • Design patterns

  • Component architecture

  • Application lifecycle concerns

Some of the architectural decisions I made back then are decisions I would not make today.

But that is part of the value of the project.

You learn a lot when you have to live with your own architectural decisions for several years.

Why I am preserving EvolvePHP 1

When I decided to begin EvolvePHP 2, one question was whether I should simply transform the existing codebase into the new framework.

I decided against that.

EvolvePHP 1 represents a particular period in my development career and a particular era of PHP development.

There is value in preserving that history.

It shows where the project started.

It also gives me a reference point when designing EvolvePHP 2.

There may be ideas worth revisiting.

There may also be decisions that serve as useful reminders of what not to repeat.

And practically, preserving the existing version costs me very little.

So EvolvePHP 1 remains preserved rather than being overwritten by EvolvePHP 2.

Why EvolvePHP 2 is a redesign instead of an upgrade

A lot has changed since I last worked seriously on EvolvePHP 1.

PHP has changed significantly.

The ecosystem has changed.

The way applications are deployed has changed.

My own understanding of software architecture has changed.

EvolvePHP 1 was designed in an era where a typical PHP application was largely expected to handle one request, produce a response, and terminate.

Modern applications increasingly operate around:

  • Containers

  • Cloud infrastructure

  • Persistent workers

  • Queues

  • Background jobs

  • APIs

  • Distributed systems

  • Observability

  • OpenTelemetry

  • CI/CD

  • Horizontal scaling

  • Docker

  • Kubernetes

  • Long-running PHP runtimes

Trying to force all of that into the original EvolvePHP architecture would create more problems than it solves.

Some of the dependencies are outdated.

Some design decisions belong to an older PHP ecosystem.

And EvolvePHP 1 is much more comfortable around the PHP 7 generation than the environment I want EvolvePHP 2 to target.

So EvolvePHP 2 is not:

EvolvePHP 1
    +
new features

It is closer to:

Lessons from EvolvePHP 1
        +
14+ years of engineering experience
        +
modern PHP
        +
modern infrastructure
        +
new architectural principles
        ↓
EvolvePHP 2

That distinction is important.

The problem I want EvolvePHP 2 to solve

Another thing that has become clearer to me over the years is how difficult software modernization can be.

There are many PHP applications still running important businesses today.

Some were built with older frameworks.

Some are custom applications.

Some are based on versions of PHP that companies would like to move away from.

But rewriting a large production system from scratch is often unrealistic.

A company may have years of business logic inside an application.

Thousands of database records.

Integrations with other systems.

Customers actively using it.

Employees depending on it every day.

Telling that business:

"Rewrite everything."

is rarely useful advice.

That is one of the problems I want EvolvePHP 2 to explore more seriously.

What if a modern framework could help developers gradually improve an existing application?

Instead of:

Legacy System
      ↓
Complete Rewrite
      ↓
Modern System

the process could look more like:

Existing System
      │
      ├── Existing modules
      ├── Existing business logic
      │
      └── Evolve Bridge
              │
              ├── New capability
              ├── Modernized module
              └── New services

Modernization could happen incrementally.

One capability at a time.

Building for change

That thinking has started shaping the philosophy behind EvolvePHP 2.

The goal is not simply to build another framework with routing, controllers and dependency injection.

Those problems have already been solved extremely well by frameworks such as Laravel and Symfony.

The more interesting question is:

How do we design applications that are easier to change several years from now?

That means thinking about things such as:

  • Modular application architecture

  • Clear package boundaries

  • Explicit dependency contracts

  • Safe execution lifecycles

  • Persistent-worker safety

  • Plugin architecture

  • Observability

  • Incremental modernization

  • Framework interoperability

  • Service extraction

  • Upgrade safety

A new application may start as one modular monolith.

Later, one module may need to scale independently.

An old application may gradually move functionality into modern modules.

A company may want to adopt modern PHP without immediately replacing everything that already works.

EvolvePHP 2 is being designed around those realities.

Doing it differently this time

There is another major difference between how I built the original framework and how I am approaching EvolvePHP 2.

EvolvePHP 1 grew mostly through implementation.

EvolvePHP 2 is starting with architecture.

Before implementing major framework features, I am defining the decisions that those features must follow.

That includes architecture RFCs covering areas such as:

  • Framework vision and scope

  • Package boundaries

  • Versioning and compatibility

  • Module and plugin lifecycle

  • Execution scope and runtime reset

  • Incremental modernization through Evolve Bridge

  • Observability and OpenTelemetry

I am also applying a test-driven approach to the framework itself.

Instead of simply adding a feature because it appears to work, I want important framework behavior to have explicit tests and architectural rules around it.

The goal is to make accidental architectural drift harder.

Looking back before moving forward

EvolvePHP 1 was never perfect.

I wouldn't build it the same way today.

But I don't regret building it.

It taught me things that I probably would never have learned by only using other people's frameworks.

It forced me to understand what happens underneath the abstractions I normally depended on.

And most importantly, it gave me something real to learn from.

EvolvePHP 2 is therefore not an attempt to erase the original project.

It is the next step in its evolution.

The first version was built from the experience I had at the time.

The second version is being built from everything I have learned since.

And this time, the ambition is broader:

Build a PHP framework designed not only for creating applications, but for helping those applications evolve over time.

That is the journey I will be documenting as EvolvePHP 2 develops.