Modernize a Legacy PHP Application

Viewing 12 posts - 1 through 12 (of 12 total)
  • Author
  • #5605

    Modernize a Legacy PHP Application


    Also big points at the end, especially in that second link
    > Finally, if you can afford it, a rewrite to a modern PHP framework such as Laravel or Symfony is much better in the long run. You will have all the tools you need to do safe, sane PHP development. Depending on the size of the application, if it is big, I would advise using the [strangler pattern]( to avoid a [big bang rewrite]( that could (and probably would) end badly. This way, you can migrate parts of the code you are currently working on to the new system while keeping the old working ones untouched until you need to work on them.

    From a business perspective, time spent rewriting is time spent standing still. Unless you have a business reason (or huge glaring security flaw), don’t spend time rewriting it unless you’re touching it for good reason such as adding new functionality that are required.

    We get a lot of new devs come in and say the big older systems are junk and need to be rewritten in x framework/language, however they vastly underestimate the work to mature a large codebase to the same working reliable point.


    Good post.

    Adding to #1 you’ll also want to not use `.env` for production (either bake stuff into code/config on deploy or get it from some kind of vault).

    Also worth noting: by fixing #2 you can also start to set up testing stuff and gradually introduce tests for stuff you’re about to change/fix/refactor.


    Having gone through this and the years of work it took, I can absolutely relate to the points you’ve made. There are others as well.

    – Implement a DI container so you can more easily manage services and construction to keep things more uniform. Also, this will help you by having a place to encourage immutability within services.

    – Implement repository services for queries. Centralizing your query logic allows for a single place to update core query logic – things like multi-tenancy get easier.

    – Global exception handling. Most legacy apps don’t have a good way of handling errors. Set this up first thing, that way you’re sure you’re catching all errors, every one of them, and returning responses in a unified fashion, coupled with reliable logging. This will make everything else you do much easier. Refactoring legacy apps is highly error prone. You’ll need great error handling snd logging. Don’t skip this step.

    – Be sure sure to start throwing exceptions everywhere in your code. Early and specific Exceptions make debugging easier. Start checking types and state within procedural parts of code, throwing exceptions when it’s not as expected. The more of this, the better. You’re likely dealing with too much nullable state, since that was typical of older PHP code.

    – There are many other great coding patterns that are helpful. Adopting a few of these as a way to focus and clean up existing ways of handling logic, will give you a place to begin cleanup, and a motive. Factories, for example could prove very useful in cleaning up model construction. Just keep a look out for patterns.

    I’ve thought about writing a big blog piece on how we moved from a Symfony1 web app to a custom designed GraphQL API. Not sure how much interest people would have on that topic though.


    There’s a book about this that’s pretty good. Recommended.


    I found so many similarities to an application I am currently working on at my job. I’ve started to steer the course, as an architect, to a more modern way, but basically I have experience on all the topics you listed:

    **1: Credentials in the code**

    – Yup. We started working on this after a migration to AWS and for some reason senior developers thought it was better to develop a more complex system for environment variables (multiple layers) from a PHP-environment file, very similar to .env, to system variables in the databases and some stored in Parameter Store. It is also discouraged to extend the environment file as we have too many environments running and it’s too much work. While I like having our secrets stored inside Parameter Store, this layered solution requires us to visit all three locations to find out where the variable is located at. It blows my mind every time I think about this solution. I’ve introduced some solutions to this but at the current speed we’ll fix it in 2022.

    **2: Not using Composer**

    – Another thing I stumbled on. We did have composer installed but not utilized to the fullest extent. I changed it so we use more open source libraries that have been proven to work and we also develop our own. I also drove us to use Private Packagist too, so we could have internal packages (closed source) installed across multiple repositories.

    **3: No local**

    – We used to have an online development server, but now we use Docker (thank god). We did have resistance with the senior developers and we still have people who rather develop online using EC2 instances daily

    **4: Not using a public folder**

    – Yup. I am attempting to remedy this by having us move to an architecture using a framework that allows it. We’re still years away from this, however, and given that the business side is driving decisions on refactoring it might not even happen.

    **5: Blatant Security Issues**

    – Not really blatant, but rather issues exist due to poor architectural choices made in the past.

    **6: No tests**

    – Well… there are tests. But they are mostly functional. Massive functional tests that go through several components. Collections take hours to run through. I’ve recently implemented ways for people to start writing unit tests and introduced them to mocking. Again, senior developers who are set in their ways resist change and rather write functional tests, as they feel like unit tests don’t cover enough.

    **7: Poor error handling**

    – Yeah, nothing like “There was a database error, please contact our customer support” on your debug logs to find out what the f went wrong. We now have proper logging, but it wasn’t always like that. Some errors are still too obscure to even make sense of. It’s improving, however.

    **8: Global variables.**

    – Well, not so much variables but dependencies due to the poor non-existent architecture. No namespace and no injections, just scripts and classes, so one legacy file requires another and that requires 10 more so in the end you will always find a file that has a dependency to require a database connection or a connection to Parameter Store or something else. I’m attempting to mock our database global singleton instance to make it possible to mock the entire connection so we can run more unit tests, but this is really painful.

    We are currently refactoring the code on a daily basis and also implementing the strangler pattern, hoping to shift it to modern standards but at our current pace of things it’ll be completely done in 2030. I might be working at another company by then if I get too frustrated with the way things are going.


    This is a subject near and dear to me, so much that I wrote a book about it: [Modernizing Legacy Applications in PHP]( It gives a detailed step-by-step approach to follow, and covers (directly or indirectly) many of the items in the OP, along with much more. Nice to see other people hitting on similar points!


    This is a good list, but the author forgot step 1: make sure your app has a passing and somewhat deep integration or functional test suite. Modernizing an app shouldn’t break the app. A good test suite can guard against that.


    If I see _any_ on half of the points mentioned in a potential company / hire situation, it’s time to nope out. Red flags in 2020 everywhere, _unless_ I was hired for that specific case.


    I’m using a method similar to #1, but in PHP, enabling me to set up arbitrary conditions and “trickle down effects” on the configuration. It checks domain and other conditions to control what configuration is expressed in a specific installation. That means different domains (test, staging, production) can run the exact same code (from a source code point of view) with the exact same database content, making “roll-over” very easy. I can also set global debug and performance flags for the whole system, so I could perform e.g. performance testing on staging while that flag will automatically not be set when moving to production. It works for me.




    Another dogma based article, like “You don’t think the way I think? This means you are a sinner!”

    Point #1, #5 and #7 have nothing to do with whether the code is legacy or not.

    All other points can be disputed. Specially #8 I hear a lot and I assume this has a very “westernized” mindset and approach to coding. Number of global variables is not an issue, the important thing is how do you organize your code, if your code is not organized in a manner that takes into account global variables, no matter if you have 10 or 1000 global variables, it will always be a risk.

Viewing 12 posts - 1 through 12 (of 12 total)
  • You must be logged in to reply to this topic.