I am writing some code (a database abstraction layer, to be used in other of my code modules) that I would like to release as a standalone module that may be included in a project with composer. I would like to include in the composer defintion some suggested modules that would improve the performance of my module, but that are't required.
The problem I have is how to do this in a way that is note completely horrific.
So in this particular example the module is declared in namespace Intahwebz\DB;
and then the optional module Intahwebz\Log\Log
is tried to be included, which in turn tries to use the optional module Monolog
.
What I have so far is the module code ConnectionWrapper.php
namespace Intahwebz\DB;
use Intahwebz\Log\Log;
if(trait_exists("Intahwebz\Log\Log") == false){
require_once("Log.php");
}
class ConnectionWrapper{
use Log;
function __construct(){
$this->initLog();
// Rest of constructor code.
$this->log->trace("ConnectionWrapper has been constructed.");
}
// Lots of other functions here.
}
?>
Then in Log.php I check to see if Monolog is available and if so include it, otherwise define a really lightweight logger.
<?php
namespace Intahwebz\Log;
if (class_exists('Monolog\Logger') &&
class_exists('Monolog\Handler\StreamHandler')) {
require_once "MonologWrapper.php";
}
else{
class Logger{
public function debug($message, array $context = array()){
echo $message."
";
}
public function log($level, $message, array $context = array()){
echo $message."
";
}
public function info($message, array $context = array()){
echo $message."
";
}
public function notice($message, array $context = array()){
echo $message."
";
}
public function warning($message, array $context = array()){
echo $message."
";
}
public function error($message, array $context = array()){
echo $message."
";
}
public function critical($message, array $context = array()){
echo $message."
";
}
public function alert($message, array $context = array()){
echo $message."
";
}
public function emergency($message, array $context = array()){
echo $message."
";
}
}
trait Log{
var $log;
function initLog(){
$this->log = new Logger(__CLASS__);
}
}
}
If Monolog is available, we use it by including MonologWrapper.php
<?php
namespace Intahwebz\Log;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
trait Log{
var $log;
function initLog(){
$this->log = new Logger(__CLASS__);
//Todo - get log handler from config file automagically.
$this->log->pushHandler(new StreamHandler(PATH_TO_ROOT.'var/log/Admin.log', Logger::WARNING));
}
}
?>
The problems with this are:
1) It's incredibly ugly, and requires an extra files per suggested module.
2) It doesn't allow people to switch in a different logger other than monolog without re-writing code.
3) It has duplicate class/trait definitions separated only by if statements, which completely confuses my IDE.
I know that the way Symfony et al solve this problem is by having a service layer. However I can't see how to use that design pattern without forcing the dependency inclusions to be a lot more complicated than they ought to be for just bringing in an optional module.
Can anyone describe a decent design pattern for including optional modules, or replacing them with other compatible modules? Or is this the type of thing that can only be defined nicely within an actual framework?