diff --git a/UPGRADE.md b/UPGRADE.md index dfbb1e55..81b6e90b 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -6,3 +6,12 @@ - Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn`. + +- `HandlerInterface` has been split off and two new interfaces now exist for + more granular controls: `ProcessableHandlerInterface` and + `FormattableHandlerInterface`. Handlers not extending `AbstractHandler` + should make sure to implement the relevant interfaces. + +- `HandlerInterface` now requires the `close` method to be implemented. This + only impacts you if you implement the interface yourself, but you can extend + the new `Monolog\Handler\Handler` base class. diff --git a/src/Monolog/Handler/AbstractHandler.php b/src/Monolog/Handler/AbstractHandler.php index 525e4f99..f5a7f5fb 100644 --- a/src/Monolog/Handler/AbstractHandler.php +++ b/src/Monolog/Handler/AbstractHandler.php @@ -12,25 +12,17 @@ namespace Monolog\Handler; use Monolog\Logger; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; /** - * Base Handler class providing the Handler structure + * Base Handler class providing basic level/bubble support * * @author Jordi Boggiano */ -abstract class AbstractHandler implements HandlerInterface +abstract class AbstractHandler extends Handler { protected $level = Logger::DEBUG; protected $bubble = true; - /** - * @var FormatterInterface - */ - protected $formatter; - protected $processors = array(); - /** * @param int $level The minimum logging level at which this handler will be triggered * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not @@ -49,72 +41,6 @@ abstract class AbstractHandler implements HandlerInterface return $record['level'] >= $this->level; } - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - foreach ($records as $record) { - $this->handle($record); - } - } - - /** - * Closes the handler. - * - * This will be called automatically when the object is destroyed - */ - public function close() - { - } - - /** - * {@inheritdoc} - */ - public function pushProcessor($callback) - { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } - array_unshift($this->processors, $callback); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function popProcessor() - { - if (!$this->processors) { - throw new \LogicException('You tried to pop from an empty processor stack.'); - } - - return array_shift($this->processors); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(FormatterInterface $formatter) - { - $this->formatter = $formatter; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - if (!$this->formatter) { - $this->formatter = $this->getDefaultFormatter(); - } - - return $this->formatter; - } - /** * Sets minimum logging level at which this handler will be triggered. * @@ -162,23 +88,4 @@ abstract class AbstractHandler implements HandlerInterface { return $this->bubble; } - - public function __destruct() - { - try { - $this->close(); - } catch (\Exception $e) { - // do nothing - } - } - - /** - * Gets the default formatter. - * - * @return FormatterInterface - */ - protected function getDefaultFormatter() - { - return new LineFormatter(); - } } diff --git a/src/Monolog/Handler/AbstractProcessingHandler.php b/src/Monolog/Handler/AbstractProcessingHandler.php index 6f18f72e..05ff3a88 100644 --- a/src/Monolog/Handler/AbstractProcessingHandler.php +++ b/src/Monolog/Handler/AbstractProcessingHandler.php @@ -19,8 +19,11 @@ namespace Monolog\Handler; * @author Jordi Boggiano * @author Christophe Coevoet */ -abstract class AbstractProcessingHandler extends AbstractHandler +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + /** * {@inheritdoc} */ @@ -30,7 +33,9 @@ abstract class AbstractProcessingHandler extends AbstractHandler return false; } - $record = $this->processRecord($record); + if ($this->processors) { + $record = $this->processRecord($record); + } $record['formatted'] = $this->getFormatter()->format($record); @@ -46,21 +51,4 @@ abstract class AbstractProcessingHandler extends AbstractHandler * @return void */ abstract protected function write(array $record); - - /** - * Processes a record. - * - * @param array $record - * @return array - */ - protected function processRecord(array $record) - { - if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - } - - return $record; - } } diff --git a/src/Monolog/Handler/BufferHandler.php b/src/Monolog/Handler/BufferHandler.php index 72f89535..350a7622 100644 --- a/src/Monolog/Handler/BufferHandler.php +++ b/src/Monolog/Handler/BufferHandler.php @@ -21,8 +21,10 @@ use Monolog\Logger; * * @author Christophe Coevoet */ -class BufferHandler extends AbstractHandler +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + protected $handler; protected $bufferSize = 0; protected $bufferLimit; @@ -70,9 +72,7 @@ class BufferHandler extends AbstractHandler } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } $this->buffer[] = $record; diff --git a/src/Monolog/Handler/FilterHandler.php b/src/Monolog/Handler/FilterHandler.php index 2a0f7fd1..cd7a0f84 100644 --- a/src/Monolog/Handler/FilterHandler.php +++ b/src/Monolog/Handler/FilterHandler.php @@ -21,8 +21,10 @@ use Monolog\Logger; * @author Hennadiy Verkh * @author Jordi Boggiano */ -class FilterHandler extends AbstractHandler +class FilterHandler extends Handler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + /** * Handler or factory callable($record, $this) * @@ -113,9 +115,7 @@ class FilterHandler extends AbstractHandler } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } $this->handler->handle($record); diff --git a/src/Monolog/Handler/FingersCrossedHandler.php b/src/Monolog/Handler/FingersCrossedHandler.php index 30a85dd6..22ae0457 100644 --- a/src/Monolog/Handler/FingersCrossedHandler.php +++ b/src/Monolog/Handler/FingersCrossedHandler.php @@ -27,8 +27,10 @@ use Monolog\Logger; * * @author Jordi Boggiano */ -class FingersCrossedHandler extends AbstractHandler +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + protected $handler; protected $activationStrategy; protected $buffering = true; @@ -85,9 +87,7 @@ class FingersCrossedHandler extends AbstractHandler public function handle(array $record) { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } if ($this->buffering) { diff --git a/src/Monolog/Handler/FormattableHandlerInterface.php b/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 00000000..34025339 --- /dev/null +++ b/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/src/Monolog/Handler/FormattableHandlerTrait.php b/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 00000000..59d59cbe --- /dev/null +++ b/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + /** + * @var FormatterInterface + */ + protected $formatter; + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/src/Monolog/Handler/GroupHandler.php b/src/Monolog/Handler/GroupHandler.php index 99384d35..f06f069f 100644 --- a/src/Monolog/Handler/GroupHandler.php +++ b/src/Monolog/Handler/GroupHandler.php @@ -16,8 +16,10 @@ namespace Monolog\Handler; * * @author Lenar Lõhmus */ -class GroupHandler extends AbstractHandler +class GroupHandler extends Handler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + protected $handlers; /** @@ -56,9 +58,7 @@ class GroupHandler extends AbstractHandler public function handle(array $record) { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { diff --git a/src/Monolog/Handler/Handler.php b/src/Monolog/Handler/Handler.php new file mode 100644 index 00000000..3af01fa8 --- /dev/null +++ b/src/Monolog/Handler/Handler.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } + } +} diff --git a/src/Monolog/Handler/HandlerInterface.php b/src/Monolog/Handler/HandlerInterface.php index d920c4ba..a19846ca 100644 --- a/src/Monolog/Handler/HandlerInterface.php +++ b/src/Monolog/Handler/HandlerInterface.php @@ -11,8 +11,6 @@ namespace Monolog\Handler; -use Monolog\Formatter\FormatterInterface; - /** * Interface that all Monolog Handlers must implement * @@ -59,32 +57,12 @@ interface HandlerInterface public function handleBatch(array $records); /** - * Adds a processor in the stack. + * Closes the handler. * - * @param callable $callback - * @return self - */ - public function pushProcessor($callback); - - /** - * Removes the processor on top of the stack and returns it. + * This will be called automatically when the object is destroyed if you extend Monolog\Handler\Handler * - * @return callable + * Implementations have to be indempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. */ - public function popProcessor(); - - /** - * Sets the formatter. - * - * @param FormatterInterface $formatter - * @return self - */ - public function setFormatter(FormatterInterface $formatter); - - /** - * Gets the formatter. - * - * @return FormatterInterface - */ - public function getFormatter(); + public function close(); } diff --git a/src/Monolog/Handler/NullHandler.php b/src/Monolog/Handler/NullHandler.php index 4b845883..755ef698 100644 --- a/src/Monolog/Handler/NullHandler.php +++ b/src/Monolog/Handler/NullHandler.php @@ -21,14 +21,24 @@ use Monolog\Logger; * * @author Jordi Boggiano */ -class NullHandler extends AbstractHandler +class NullHandler extends Handler { + private $level; + /** * @param int $level The minimum logging level at which this handler will be triggered */ - public function __construct($level = Logger::DEBUG) + public function __construct(int $level = Logger::DEBUG) { - parent::__construct($level, false); + $this->level = $level; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; } /** diff --git a/src/Monolog/Handler/ProcessableHandlerInterface.php b/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 00000000..769c28b4 --- /dev/null +++ b/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor(callable $callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); +} diff --git a/src/Monolog/Handler/ProcessableHandlerTrait.php b/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 00000000..2079b632 --- /dev/null +++ b/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + */ + protected $processors = []; + + /** + * {@inheritdoc} + */ + public function pushProcessor(callable $callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } +} diff --git a/src/Monolog/Handler/SamplingHandler.php b/src/Monolog/Handler/SamplingHandler.php index 9509ae37..5e0a75e7 100644 --- a/src/Monolog/Handler/SamplingHandler.php +++ b/src/Monolog/Handler/SamplingHandler.php @@ -25,8 +25,10 @@ namespace Monolog\Handler; * @author Bryan Davis * @author Kunal Mehta */ -class SamplingHandler extends AbstractHandler +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface { + use ProcessableHandlerTrait; + /** * @var callable|HandlerInterface $handler */ @@ -69,9 +71,7 @@ class SamplingHandler extends AbstractHandler } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + $record = $this->processRecord($record); } $this->handler->handle($record); diff --git a/tests/Monolog/Handler/AbstractHandlerTest.php b/tests/Monolog/Handler/AbstractHandlerTest.php index 568eb9da..17b7eef0 100644 --- a/tests/Monolog/Handler/AbstractHandlerTest.php +++ b/tests/Monolog/Handler/AbstractHandlerTest.php @@ -13,7 +13,6 @@ namespace Monolog\Handler; use Monolog\TestCase; use Monolog\Logger; -use Monolog\Formatter\LineFormatter; use Monolog\Processor\WebProcessor; class AbstractHandlerTest extends TestCase @@ -24,8 +23,6 @@ class AbstractHandlerTest extends TestCase * @covers Monolog\Handler\AbstractHandler::setLevel * @covers Monolog\Handler\AbstractHandler::getBubble * @covers Monolog\Handler\AbstractHandler::setBubble - * @covers Monolog\Handler\AbstractHandler::getFormatter - * @covers Monolog\Handler\AbstractHandler::setFormatter */ public function testConstructAndGetSet() { @@ -35,10 +32,8 @@ class AbstractHandlerTest extends TestCase $handler->setLevel(Logger::ERROR); $handler->setBubble(true); - $handler->setFormatter($formatter = new LineFormatter); $this->assertEquals(Logger::ERROR, $handler->getLevel()); $this->assertEquals(true, $handler->getBubble()); - $this->assertSame($formatter, $handler->getFormatter()); } /** @@ -72,44 +67,4 @@ class AbstractHandlerTest extends TestCase $handler->setLevel('debug'); $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); } - - /** - * @covers Monolog\Handler\AbstractHandler::getFormatter - * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter - */ - public function testGetFormatterInitializesDefault() - { - $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); - $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); - } - - /** - * @covers Monolog\Handler\AbstractHandler::pushProcessor - * @covers Monolog\Handler\AbstractHandler::popProcessor - * @expectedException LogicException - */ - public function testPushPopProcessor() - { - $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); - $processor1 = new WebProcessor; - $processor2 = new WebProcessor; - - $logger->pushProcessor($processor1); - $logger->pushProcessor($processor2); - - $this->assertEquals($processor2, $logger->popProcessor()); - $this->assertEquals($processor1, $logger->popProcessor()); - $logger->popProcessor(); - } - - /** - * @covers Monolog\Handler\AbstractHandler::pushProcessor - * @expectedException InvalidArgumentException - */ - public function testPushProcessorWithNonCallable() - { - $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); - - $handler->pushProcessor(new \stdClass()); - } } diff --git a/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/tests/Monolog/Handler/AbstractProcessingHandlerTest.php index 24d4f63c..40f459b6 100644 --- a/tests/Monolog/Handler/AbstractProcessingHandlerTest.php +++ b/tests/Monolog/Handler/AbstractProcessingHandlerTest.php @@ -14,9 +14,21 @@ namespace Monolog\Handler; use Monolog\TestCase; use Monolog\Logger; use Monolog\Processor\WebProcessor; +use Monolog\Formatter\LineFormatter; class AbstractProcessingHandlerTest extends TestCase { + /** + * @covers Monolog\Handler\FormattableHandlerTrait::getFormatter + * @covers Monolog\Handler\FormattableHandlerTrait::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertSame($formatter, $handler->getFormatter()); + } + /** * @covers Monolog\Handler\AbstractProcessingHandler::handle */ @@ -77,4 +89,44 @@ class AbstractProcessingHandlerTest extends TestCase $handler->handle($this->getRecord()); $this->assertEquals(6, count($handledRecord['extra'])); } + + /** + * @covers Monolog\Handler\ProcessableHandlerTrait::pushProcessor + * @covers Monolog\Handler\ProcessableHandlerTrait::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\ProcessableHandlerTrait::pushProcessor + * @expectedException TypeError + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + + $handler->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Handler\FormattableHandlerTrait::getFormatter + * @covers Monolog\Handler\FormattableHandlerTrait::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $this->assertInstanceOf(LineFormatter::class, $handler->getFormatter()); + } }