<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Filipe Silva</title>
    <description>The latest articles on Forem by Filipe Silva (@silvafilipe).</description>
    <link>https://forem.com/silvafilipe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3895087%2F5307a470-6907-4fbe-ba24-5bcff8e74be4.png</url>
      <title>Forem: Filipe Silva</title>
      <link>https://forem.com/silvafilipe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/silvafilipe"/>
    <language>en</language>
    <item>
      <title>The PHP Criticism You've Never Actually Thought Through</title>
      <dc:creator>Filipe Silva</dc:creator>
      <pubDate>Tue, 28 Apr 2026 12:49:13 +0000</pubDate>
      <link>https://forem.com/silvafilipe/the-php-criticism-youve-never-actually-thought-through-1jib</link>
      <guid>https://forem.com/silvafilipe/the-php-criticism-youve-never-actually-thought-through-1jib</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — I've been writing PHP for 15 years. I've tried Ruby, Groovy, and Java. I came back each time. This article goes through the serious criticisms of PHP — stdlib inconsistency, the stateless model, no real async — and where each one actually holds up, and where it doesn't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been writing PHP for over 15 years. I've tried Ruby, Groovy, and Java — not out of curiosity, but because I genuinely questioned whether PHP was the right tool. I came back each time. Not out of habit, but because the arguments against PHP rarely survive contact with reality.&lt;/p&gt;

&lt;p&gt;Let me go through the serious ones. And where PHP genuinely falls short, I'll say so.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, some context most critics skip
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The people who say PHP can't evolve stopped paying attention around 2005. The language kept moving without them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP was created in 1994 by Rasmus Lerdorf. It didn't start as a programming language — it was a set of scripts to monitor visits to his online résumé. Built to solve a real problem, not designed by a committee with an architectural vision.&lt;/p&gt;

&lt;p&gt;Critics use this against it: "No coherent design from the start." Fair. Growing organically has costs. But what followed was not stagnation — it was sustained evolution over three decades.&lt;/p&gt;

&lt;p&gt;PHP 3.0 gave the language a proper modular foundation. PHP 4 brought the Zend Engine and the first real object-oriented support. PHP 5 made OOP actually usable — abstract classes, interfaces, exceptions, PDO. These weren't small updates. They were the language growing up.&lt;/p&gt;

&lt;p&gt;PHP 7 was the version that changed things for me personally. Performance roughly doubled over 5.6. Memory consumption dropped. Scalar type declarations arrived. For the first time I could apply design patterns and architectural concepts — DDD, hexagonal architecture, proper interfaces — without fighting the language to do it. PHP 7.4 in particular felt like a turning point: typed properties, arrow functions, a language that finally matched the way I wanted to think about code.&lt;/p&gt;

&lt;p&gt;PHP 8 continued from there — JIT compilation, union types, match expressions, attributes, named arguments. PHP 8.3 and 8.4 added typed class constants, readonly improvements, property hooks. The people who say PHP can't evolve stopped paying attention around 2005. The language kept moving without them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stdlib is inconsistent. Yes. But that's not the argument you think it is.
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The inconsistencies are being removed, version by version. That's the cost of scale, and PHP pays it responsibly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The standard library grew alongside the language — functions added at different times, by different contributors, with different conventions. The result is real: inconsistent naming, parameter ordering that flips between functions. needle before haystack in some, the reverse in others.&lt;/p&gt;

&lt;p&gt;Some defend this by pointing to IDEs. I won't — that's pointing to a workaround, not answering the criticism. The real argument is simpler: the inconsistencies are being removed, version by version. Slowly — because PHP takes backwards compatibility seriously. A significant portion of the web runs on PHP. Breaking it carelessly is not an option. That's the cost of scale, and PHP pays it responsibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The share-nothing model is a limitation. It's also a structural guarantee.
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The stateless model doesn't make you a better developer. It removes one category of mistake from the table by design.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP's execution model is stateless by default — each request starts fresh, no shared memory between them. The performance cost is real. JIT and caching address most of it in practice. But the cost exists.&lt;/p&gt;

&lt;p&gt;What rarely gets mentioned is what you get in return. Stateless execution makes an entire class of memory problems structurally harder to produce. I've had a Groovy application stop completely because the JVM held on to state it should have released. Managing long-lived state is genuinely hard. The stateless model removes one category of mistake from the table by design.&lt;/p&gt;

&lt;p&gt;A PHP setup is also notably lighter than a JVM or Node runtime. In environments where infrastructure costs matter — and they usually do — that's not a small detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  No threads, no real async. Here's where I'll be honest.
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Knowing where your tool's boundaries are is part of using it well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP doesn't have native threads or true async execution. Fibers, added in PHP 8.1, are cooperative — not parallel. This is a real limitation.&lt;/p&gt;

&lt;p&gt;If you're building long-running workers that need shared state and true parallelism — background processors, event-driven services, persistent connections — PHP is not the right tool. I'd use Java. Threads, shared state, long-lived processes: that's Java's territory.&lt;/p&gt;

&lt;p&gt;But that's a specific problem. For web applications — APIs, sites, request-response cycles — native threads rarely come up. Apache manages concurrent connections. Queues handle background work. Most applications that think they need threads are actually describing an infrastructure problem. PHP delegates concurrency to the layer better suited to handle it. That's not a gap — that's a boundary. Knowing where your tool's boundaries are is part of using it well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java doesn't dominate the web. Nobody calls it dead.
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;PHP outlasted languages that came specifically to replace it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Java is mature, strongly typed, has a massive ecosystem. By most objective measures it's an excellent language for web development. It never dominated the web the way PHP has. Nobody eulogises it for that.&lt;/p&gt;

&lt;p&gt;PHP outlasted languages that came specifically to replace it. Ruby on Rails had its moment — around 2005 to 2010 — when it felt like it might displace PHP entirely. It didn't. PHP is still here, still powering a significant share of the web, still getting faster with each major version.&lt;/p&gt;

&lt;h2&gt;
  
  
  I tried to leave
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The language that gets mocked is not the language I use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ruby taught me that convention over configuration is a real productivity gain. Groovy showed me a more expressive JVM. Java gave me appreciation for strict typing in complex systems — and it's still my choice for long-running processes with shared state.&lt;/p&gt;

&lt;p&gt;What brought me back wasn't sentiment. PHP kept getting better at what it's actually for. What I write today in PHP 8+ — strict types, PHPStan at maximum level, DDD with hexagonal architecture — looks nothing like what people picture when they say "PHP is bad." The language that gets mocked is not the language I use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual problem
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;No compiler catches logic errors. You catch them by understanding the problem deeply.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most PHP criticism conflates three things: the language, the infrastructure around it, and the code people write with it. A poorly written WordPress plugin is not evidence that PHP is bad. It's evidence that accessible languages attract developers at every level. That's not a flaw — that's what happens when a language is approachable.&lt;/p&gt;

&lt;p&gt;The hardest bugs aren't type errors or concurrency issues. They're logic errors — misunderstood requirements, wrong abstractions, bad domain models. No compiler catches those. You catch them by understanding the problem deeply, regardless of what's on the left side of your shebang line.&lt;/p&gt;




&lt;p&gt;Dead or not, PHP is the language I choose for my projects and teach with conviction. Thirty years in, it's still shipping. So am I.&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Migrating a PHP monolith in production: how I think about it</title>
      <dc:creator>Filipe Silva</dc:creator>
      <pubDate>Fri, 24 Apr 2026 17:08:36 +0000</pubDate>
      <link>https://forem.com/silvafilipe/migrating-a-php-monolith-in-production-how-i-think-about-it-26k7</link>
      <guid>https://forem.com/silvafilipe/migrating-a-php-monolith-in-production-how-i-think-about-it-26k7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — A security breach took down a site running on a 10-year-old Debian server with an obsolete LAMP stack. No access to the original infrastructure, no time to understand it. This is what I did, in what order, and why. The system is still in production. The migration is still ongoing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The site went down because of a security breach.&lt;/p&gt;

&lt;p&gt;Not a gradual degradation, not a performance problem we'd been ignoring. A breach. The system was offline, and getting back in meant dealing with infrastructure that hadn't been touched in over a decade — a Debian server, no longer receiving updates, running a LAMP stack that had been obsolete for years. No access to the original setup. No time to understand it properly. The only priority was getting it back up.&lt;/p&gt;

&lt;p&gt;That's where this started.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I was looking at
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A system doesn't have to be old to become unmaintainable. Five to seven years of incremental pressure is enough.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system was 5 to 7 years old, but the infrastructure underneath it was older. When you run something on a server for long enough without revisiting it, the server becomes part of the system — not explicitly, but in practice. Things depend on the exact PHP version, the exact MySQL version, the exact way that particular Debian release handled file permissions. Nobody documented any of it because nobody needed to. It worked.&lt;/p&gt;

&lt;p&gt;Until it didn't.&lt;/p&gt;

&lt;p&gt;The codebase itself was mixed. Some parts had structure — someone had clearly tried to impose order at some point. Around those parts, other things had grown organically: patches, workarounds, logic that existed because of a constraint that no longer applied. The kind of code that's perfectly explicable if you were there when it was written, and completely opaque if you weren't.&lt;/p&gt;

&lt;p&gt;Adding a feature meant touching things you weren't sure about. Changing one module broke another. The system worked, but it had become fragile in a way that wasn't visible until something forced you to look.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we actually did
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The breach removed every argument against changing the infrastructure. Nobody could say "not now" anymore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the site offline, the immediate goal was obvious: get it back up. Not rewrite it — restore it. Continuity first.&lt;/p&gt;

&lt;p&gt;But the breach also opened a door that had been closed. The second decision — taken at the same time, not later — was to migrate everything to Kubernetes. Not to modernize the application code immediately, but to get control of the infrastructure. The existing LAMP stack, obsolete versions and all, would be containerized and run on k8s. From there, new modules could be introduced gradually, sitting alongside the old system, routed through ingress rules, replacing behaviour piece by piece.&lt;/p&gt;

&lt;p&gt;That's the Strangler Fig pattern. You don't kill the old system. You build the new one around it, redirect traffic module by module, and remove the old pieces when they're no longer needed. The fig grows around the tree.&lt;/p&gt;




&lt;h2&gt;
  
  
  The sequence
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The order wasn't arbitrary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the site was stable again, the work followed a specific order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; — the components with direct user exposure. There's no point modernizing the backend if the frontend is slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscriptions and payments&lt;/strong&gt; — migrating to Stripe, cleaning up a part of the business that had become technically fragile and commercially important.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; — moving to OIDC and OAuth 2.0. Not urgent on its own, but without it, any future integration would be harder than it needed to be.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else&lt;/strong&gt; — the remaining modules, refactored into the new architecture as capacity and priority allowed.
Each step could be shipped independently. Each one made the next one less complicated.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What the new code looks like
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The decision was made once, at the beginning, and applied consistently. Introducing patterns gradually and inconsistently produces a different kind of mess.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything written after the decision to modernize runs on PHP 8.x, follows Domain-Driven Design principles, and is structured around a hexagonal architecture.&lt;/p&gt;

&lt;p&gt;DDD has a cost. You have to understand the domain before you model it. You can't just translate old code into new patterns — you have to ask what the system is actually &lt;em&gt;doing&lt;/em&gt;, what the real business rules are, where the boundaries between responsibilities should sit. Those conversations take time and they're sometimes uncomfortable.&lt;/p&gt;

&lt;p&gt;But the alternative — introducing patterns gradually, inconsistently, module by module — produces a codebase that's modern in some places and legacy in others, with the boundary between them becoming its own source of confusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing I didn't expect
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The technical debt was worse than it looked. It almost always is. Stop trying to understand why things are the way they are. Focus on what they need to do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I knew the technical debt would be bad. Knowing it and finding it are different things. When you start pulling at a module, you find business logic in SQL queries, assumptions baked into the schema, side effects triggered by things that look like reads.&lt;/p&gt;

&lt;p&gt;The system wasn't badly built because nobody cared. It was built under pressure, incrementally, by people solving real problems with the tools and time they had. That's how most systems end up where they are.&lt;/p&gt;

&lt;p&gt;Once I accepted that, the approach changed. Instead of trying to understand &lt;em&gt;why&lt;/em&gt; things were the way they were, I focused on what they needed to &lt;em&gt;do&lt;/em&gt; — modelled that cleanly, and replaced the behaviour one piece at a time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I am now
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The system hasn't stopped. That's the point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some modules are fully on the new architecture. Others are still the original code, containerized but otherwise untouched, waiting their turn. New development happens almost entirely in the new structure.&lt;/p&gt;

&lt;p&gt;It's not finished. I'm not sure when it will be.&lt;/p&gt;

&lt;p&gt;If I were starting today, I'd do the same things in the same order. The one thing I'd add earlier is a testing baseline — not full coverage, just enough signal to know when something breaks that shouldn't. Without it, you rely on intuition more than is comfortable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I wrote this
&lt;/h2&gt;

&lt;p&gt;I looked for articles like this when I was in the middle of it. What I found was mostly theory — patterns described without context, case studies from companies with six months of runway and a dedicated platform team.&lt;/p&gt;

&lt;p&gt;This started with a site that was offline and a server I couldn't get into. Every decision here came from that situation, not from an ideal setup.&lt;/p&gt;

&lt;p&gt;If you're dealing with something similar — a system that works but has become the kind of thing nobody wants to touch — I hope some of this is useful. If you want to compare notes, my LinkedIn is in the profile.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about software architecture and PHP.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
