There are several valuable resources online about how to modernise a legacy application – for example, Alexander Turek (Alexander M. Turek – Modernizing with Symfony) and Anna Filina (Anna Filina – Rewriting Legacy Code) offer advice on approaching this type of project, while the insights found in books like Working Effectively with Legacy Code are also helpful. Wouldn’t it be great if you could take some steps now to prevent some of the more challenging problems you may face? Here are a few tips we have gathered based on our own experience.

1.Use class names responsibly now to streamline modernisation later

Keep in mind that class names and namespaces are not meant to be immutable. So, it should not be a major challenge to change them. Choosing to store FQCNs in the database or instantiating objects from dynamically concatenated strings throughout the codebase will complicate refactoring, leading to inconsistencies and runtime issues. 

2. Keep yourself informed 

By keeping up to date with changes to your project’s language and dependencies, you can make upgrades smoother. Make sure you are aware of upcoming backwards incompatible changes and proactively use alternatives for deprecated features.

3. Don’t reinvent any wheels when preparing for a modernisation initiative

Before investing time in a custom implementation, look for alternatives within the language or established frameworks or libraries. Aim to leverage existing, well-tested solutions instead of creating artisanal ones that may be suboptimal, introduce unnecessary complexity, and increase maintenance costs.

4. Stay ready for modernisation by steering clear of global state

It is difficult to change code that relies on static properties and methods. Static properties introduce global state into your application. Static methods will usually operate on this global state. This can have multiple unrelated side effects and complicate testing components in isolation. While there may be specific cases where static properties and methods are suitable, it is generally advisable to use instance methods.

5. Do not add to legacy inheritance trees – modernisation efforts will tend towards composition

With a deep inheritance hierarchy, modifying a base class may cause bugs and unexpected behaviour in derived classes. Testing your code also becomes a problem, as you need to consider scenarios involving inherited behaviours. Before creating a derived class, check whether it is feasible to extract the behaviour you need from its parent(s) into one or several services that you can then use, change, and test independently.

6. Make modernisation easier by using types

Declare types explicitly wherever possible. Allowing any value to have any type will be the biggest source of subtle and frustrating bugs. Make sure you restrict the types as much as possible: it is not particularly helpful to use ‘mixed’ or union types like ‘object|resource|array|string|int|float|bool|null’. Having methods that take five nullable parameters and return a nullable result is also not ideal.

7. Let static analysis tools pave the way

Integrate quality checks into your CI pipeline to analyse your changes and enforce coding standards, detect code smells, or identify deprecated/unused methods and variables. A few recommended tools include: PHP CS Fixer, PHPStan, Psalm.

By including these measures in your development process, you will be laying the foundation for a smoother modernisation of your legacy app. It is important to start cultivating an forward-thinking attitude of anticipating future pain points and taking steps to avoid them, such as minimising technical debt, implementing best practices early, and actively monitoring changes to your technological stack.