1

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:

  1. Putting it all in one package: This would pack two rather unrelated packages together and really hurt single responsibility principle.
  2. Circle reference: Not allowed by composer. Also a bad idea for several reasons and not too different from packaging them together.
  3. 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.
  4. 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.

0

In terms of implementing optional dependencies, use the following process:

  • Define standard method names and default values
  • Document how to override the default values
  • Abstract all communication between PHP and external software

For example:

Every Page Controller communicates with a Model using method names which are defined in the abstract table class, which means that any Controller can be used with any Model.

The construction and generation of all SQL statements is performed within a separate Data Access Object, with a separate class for each different DBMS (MySQL, PostgreSQL, Oracle, SQL Server). As each of these classes use identical method names I can switch from one DBMS to another without having to change any code in any of the Model classes.

Allowing methods to consume a different data type for the same argument helps as well:

Some controls, such as radio groups and dropdown lists, do not have single values but a group of options, and it is possible to have different scripting events on each option, as shown in the following:

  <select name="star_sign" onchange="alert('change');">
    <option value=""> </option>
    <option value="ARI" onkeypress="alert('1');">Aries</option>
    <option value="AQU" onkeypress="alert('2');">Aquarius</option>
    <option value="CAN" onkeypress="alert('3');">Cancer</option>
    <option value="CAP" onkeypress="alert('4');">Capricorn</option>
    <option value="GEM" onkeypress="alert('5');">Gemini</option>
    <option value="LEO" onkeypress="alert('6');">Leo</option>
    <option value="LIB" onkeypress="alert('7');">Libra</option>
    <option value="PIS" onkeypress="alert('8');">Pisces</option>
    <option value="SAG" onkeypress="alert('9');">Sagittarius</option>
    <option value="SCO" onkeypress="alert('10');">Scorpio</option>
    <option value="TAU" onkeypress="alert('11');">Taurus</option>
    <option value="VIR" onkeypress="alert('12');" selected="selected">Virgo</option>
  </select>

The options are normally supplied in an associative array consisting of a key and a value, as in:

$array['star_sign'] = array('ARI' => 'Aries',
                        ...
                        'VIR' => 'Virgo');

In order to supply an additional set of scripting events for each key it is necessary to replace the value part with an array, as in the following:

$array['star_sign'] = array('ARI' => array('value' => 'Aries',
                                           'onkeyup' => "alert('up');",
                                           'onkeydown' => "alert('down');"),
                            ...
                            'VIR' => array('value' => 'Virgo',
                                           'onkeypress' => "alert('12');"));

References

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.