Ответ 1
Где используются регистраторы?
В общем, есть два основных варианта использования логгеров в вашем коде:
-
инвазивный журнал:
По большей части люди используют этот подход, потому что это легче всего понять.
В действительности вы должны использовать только инвазивное ведение журнала, если ведение журнала является частью самой логики домена. Например, в классах, связанных с платежами или управлением конфиденциальной информацией.
-
Неинвазивное ведение журнала:
С помощью этого метода вместо изменения класса, который вы хотите зарегистрировать, вы переносите существующий экземпляр в контейнер, который позволяет отслеживать каждый обмен между экземпляром и остальной частью приложения.
Вы также можете временно включить такое ведение журнала, отлаживая какую-либо конкретную проблему за пределами среды разработки или когда вы проводите исследование поведения пользователя. Поскольку класс регистрируемого экземпляра никогда не изменяется, риск нарушения поведения проекта намного ниже по сравнению с инвазивным протоколированием.
Внедрение инвазивного регистратора
Для этого у вас есть два основных подхода. Вы можете либо ввести экземпляр, реализующий интерфейс Logger
, либо предоставить класс factory, который, в свою очередь, инициализирует систему ведения журнала только при необходимости.
Примечание:
Поскольку кажется, что прямая инъекция - это не какая-то скрытая тайна для вас, я оставлю эту часть... только я бы попросил вас избежать использования констант вне файла, где они были определены.
Теперь.. реализация с factory и ленивая загрузка.
Вы начинаете с определения API, который вы будете использовать (в идеальном мире вы начинаете с модульных тестов).
class Foobar
{
private $loggerFactory;
public function __construct(Creator $loggerFactory, ....)
{
$this->loggerFactory = $loggerFactory;
....
}
....
public function someLoggedMethod()
{
$logger = $this->loggerFactory->provide('simple');
$logger->log( ... logged data .. );
....
}
....
}
Этот factory будет иметь два дополнительных преимущества:
- он может гарантировать, что создается только один экземпляр без необходимости глобального состояния
- обеспечить шов для использования при написании модульных тестов
Примечание.. На самом деле, при написании этого способа класс Foobar зависит только от экземпляра, реализующего интерфейс Creator. Обычно вы вводите либо построитель (если вам нужно ввести экземпляр, возможно, с некоторой настройкой), либо factory (если вы хотите создать другой экземпляр с тем же интерфейсом).
Следующим шагом будет реализация factory:
class LazyLoggerFactory implements Creator
{
private $loggers = [];
private $providers = [];
public function addProvider($name, callable $provider)
{
$this->providers[$name] = $provider;
return $this;
}
public function provide($name)
{
if (array_key_exists($name, $this->loggers) === false)
{
$this->loggers[$name] = call_user_func($this->providers[$name]);
}
return $this->loggers[$name];
}
}
Когда вы вызываете $factory->provide('thing');
, factory просматривает, если экземпляр уже создан. Если поиск не выполняется, он создает новый экземпляр.
Примечание. Я действительно не совсем уверен, что это можно назвать "factory", поскольку инстанцирование действительно инкапсулировано в анонимные функции.
И последний шаг на самом деле подключить все к провайдерам:
$config = include '/path/to/config/loggers.php';
$loggerFactory = new LazyLoggerFactory;
$loggerFactory->addProvider('simple', function() use ($config){
$instance = new SimpleFileLogger($config['log_file']);
return $instance;
});
/*
$loggerFactory->addProvider('fake', function(){
$instance = new NullLogger;
return $instance;
});
*/
$test = new Foobar( $loggerFactory );
Конечно, чтобы полностью понять этот подход, вам нужно знать, как работают замыкания на PHP, но вам все равно придется их изучать.
Внедрение неинвазивного ведения журнала
Основная идея этого подхода заключается в том, что вместо ввода регистратора вы помещаете существующий экземпляр в контейнер, который действует как мембрана между указанным экземпляром и приложением. Эта мембрана может выполнять различные задачи, одна из которых регистрируется.
class LogBrane
{
protected $target = null;
protected $logger = null;
public function __construct( $target, Logger $logger )
{
$this->target = $target;
$this->logger = $logger;
}
public function __call( $method, $arguments )
{
if ( method_exists( $this->target, $method ) === false )
{
// sometime you will want to log call of nonexistent method
}
try
{
$response = call_user_func_array( [$this->target, $method],
$arguments );
// write log, if you want
$this->logger->log(....);
}
catch (Exception $e)
{
// write log about exception
$this->logger->log(....);
// and re-throw to not disrupt the behavior
throw $e;
}
}
}
Этот класс также может использоваться вместе с описанным выше ленивым factory.
Чтобы использовать эту структуру, вы просто выполняете следующее:
$instance = new Foobar;
$instance = new LogBrane( $instance, $logger );
$instance->someMethod();
В этот момент контейнер, который обертывает экземпляр, становится полностью функциональной заменой оригинала. Остальная часть вашего приложения может обрабатывать его, как если бы это был простой объект (пройдите, вызовите методы). И сам обернутый экземпляр не знает, что он регистрируется.
И если в какой-то момент вы решите удалить журнал, то это можно сделать, не переписывая остальную часть вашего приложения.