We create Templates with subjects and contents, and we use the TemplateManager to compute those templates (replace placeholders with real data where needed). Placeholders can relate to a given quote or a user. User and Quote are passed to TemplateManager through a parameter. If a user is not provided it will fetch the connected User through the ApplicationContext.
I chose to cover the code with approval testing. It is specially recommended for this type of situation when we want to be sure not to introduce behavioral changes while refactoring procedural legacy code which is not tested enough. Those tests are to be thrown away once the refactoring is done. (I had to use a fork branch of the official library because it was not compatible with PHP5.6) Once the code to refactor is covered enough we save the snapshot, and we are good to start refactoring.
Upgrade php version to 8.3. Replace the deprecated "fzaninotto/faker" by "fakerphp/faker". Upgraded phpunit and updated config accordingly. Update the approval tests snapshots as the faker's seed did not survive the library change (I should have scrubbed these data in the first time)
Introduce autoloading thanks to composer and get rid of all the "require".
Remove the singleton system to introduce Dependency injection
Introduce Service Container (and autowiring) thanks to "symfony/dependency-injection"
Update code to respect POO encapsulation principle thanks to readonly classes
Update the algorithmic using up-to-date code (new features provided by PHP earlier versions). Simplify the logic and improve the readability.
I tried to follow the sandwich pattern technique to refactor the service. It implies to put the shared states on the extremities of the method to highlight the immutable domain (the domain logic) and finally try to enrich our domain (Aggregates, Entities, ValueObjects and if needed Domain services). In that end I created a TemplatedText ValueObject to encapsulate the logic (contains and replace).
I created a system easily extensible to handle placeholder replacements in a TemplatedText. You just have to create an implementation of the App\Service\TemplateProcessorInterface. The designed solution is a mix of Chain of Responsibility and Strategy patterns. You can find solutions like this one in the Symfony codebase for Transports (Notifier) and ChainEncoder (Serializer).
The refactor is complete and the snapshot is still valid we can now remove the approval tests and be confident on our refactoring.
I added Php cs-fixer and phpstan to standardize the code quality.
I could have added a CI with GitHub actions including PhpUnit, PhpStan, Php CS-Fixer and some linting. I didn't add any new feature but validation could be interesting for the context data. We could validate the final template to ensure there is no more placeholders. And check for unused properties and methods inside the entities.