At my Job I'm currently on refactoring a very old php CMS. By now, "code handling" was done by simply copying the whole thing and modify it to fit whatever was needed for this job to be done (actually the CMS implementation itself is rather an example site than a framework for building one).
Luckily, it is pretty progressive for when it was written and already uses classes. So the first thing I did was split the CMS up in several composer packages replacing the current classes. Since there are so many copied versions, being able to drop-in replace some classes with backwards compatibility is quite important to allow an easier transition to the new system.
By now I have a package for logging and one wrapping the database class (now mysqli) with a dependency on the logging package. Currently the logging component isn't doing much more than providing a factory method to create logger instances with the usual handlers1 already set up. It should however be able to log to an external database if the database component is installed, but it shouldn't depend on it. To make matters worse, the database component has to be initialized after the logging component to alert us in case of a database failure, but errors happening before the database component is up have to be stored, too.
I thought several concepts, none of them are convincing:
- Putting it all in one package: This would pack two rather unrelated packages together and really hurt single responsibility principle.
- Circle reference: Not allowed by composer. Also a bad idea for several reasons and not too different from packaging them together.
- Use own/native database access: Easy to do, but it violates DRY and is really the exact thing I try to avoid by using composer.
- Factory: Sounds good, but it would really just create a super-package having both as a dependency. Also this would take quite a long time to get up and running, which I don't have (refactor instead of rewriting). Also, its hard to integrate into existing code and giving a part of the code base yet another flavor seems counterproductive.
My current solution is to have a MySQLHandler class in the logging package, which will always be registered as handler. The class checks for the existence of a stager class (located in the database package) and forward all errors if it exists and logging to the database is enabled. The stager itself is a singleton class which stores all messages until an activation method is called. This method then finally connects to the database and flushes the backlog (any further messages are autoflushed).
This works for now, but it feels really dirty to check for class existence and having to call an extra activation function after the logging setup. What is the correct way to handle this dependency situation?
1 Monolog works by having several instances of loggers which provide context and each of them has handlers to write log files, send mails etc.