1<?php declare(strict_types=1); 2 3/* 4 * This file is part of the Monolog package. 5 * 6 * (c) Jordi Boggiano <j.boggiano@seld.be> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Monolog\Handler; 13 14use Monolog\Logger; 15use Psr\Log\LogLevel; 16 17/** 18 * Used for testing purposes. 19 * 20 * It records all records and gives you access to them for verification. 21 * 22 * @author Jordi Boggiano <j.boggiano@seld.be> 23 * 24 * @method bool hasEmergency($record) 25 * @method bool hasAlert($record) 26 * @method bool hasCritical($record) 27 * @method bool hasError($record) 28 * @method bool hasWarning($record) 29 * @method bool hasNotice($record) 30 * @method bool hasInfo($record) 31 * @method bool hasDebug($record) 32 * 33 * @method bool hasEmergencyRecords() 34 * @method bool hasAlertRecords() 35 * @method bool hasCriticalRecords() 36 * @method bool hasErrorRecords() 37 * @method bool hasWarningRecords() 38 * @method bool hasNoticeRecords() 39 * @method bool hasInfoRecords() 40 * @method bool hasDebugRecords() 41 * 42 * @method bool hasEmergencyThatContains($message) 43 * @method bool hasAlertThatContains($message) 44 * @method bool hasCriticalThatContains($message) 45 * @method bool hasErrorThatContains($message) 46 * @method bool hasWarningThatContains($message) 47 * @method bool hasNoticeThatContains($message) 48 * @method bool hasInfoThatContains($message) 49 * @method bool hasDebugThatContains($message) 50 * 51 * @method bool hasEmergencyThatMatches($message) 52 * @method bool hasAlertThatMatches($message) 53 * @method bool hasCriticalThatMatches($message) 54 * @method bool hasErrorThatMatches($message) 55 * @method bool hasWarningThatMatches($message) 56 * @method bool hasNoticeThatMatches($message) 57 * @method bool hasInfoThatMatches($message) 58 * @method bool hasDebugThatMatches($message) 59 * 60 * @method bool hasEmergencyThatPasses($message) 61 * @method bool hasAlertThatPasses($message) 62 * @method bool hasCriticalThatPasses($message) 63 * @method bool hasErrorThatPasses($message) 64 * @method bool hasWarningThatPasses($message) 65 * @method bool hasNoticeThatPasses($message) 66 * @method bool hasInfoThatPasses($message) 67 * @method bool hasDebugThatPasses($message) 68 * 69 * @phpstan-import-type Record from \Monolog\Logger 70 * @phpstan-import-type Level from \Monolog\Logger 71 * @phpstan-import-type LevelName from \Monolog\Logger 72 */ 73class TestHandler extends AbstractProcessingHandler 74{ 75 /** @var Record[] */ 76 protected $records = []; 77 /** @var array<Level, Record[]> */ 78 protected $recordsByLevel = []; 79 /** @var bool */ 80 private $skipReset = false; 81 82 /** 83 * @return array 84 * 85 * @phpstan-return Record[] 86 */ 87 public function getRecords() 88 { 89 return $this->records; 90 } 91 92 /** 93 * @return void 94 */ 95 public function clear() 96 { 97 $this->records = []; 98 $this->recordsByLevel = []; 99 } 100 101 /** 102 * @return void 103 */ 104 public function reset() 105 { 106 if (!$this->skipReset) { 107 $this->clear(); 108 } 109 } 110 111 /** 112 * @return void 113 */ 114 public function setSkipReset(bool $skipReset) 115 { 116 $this->skipReset = $skipReset; 117 } 118 119 /** 120 * @param string|int $level Logging level value or name 121 * 122 * @phpstan-param Level|LevelName|LogLevel::* $level 123 */ 124 public function hasRecords($level): bool 125 { 126 return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); 127 } 128 129 /** 130 * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records 131 * @param string|int $level Logging level value or name 132 * 133 * @phpstan-param array{message: string, context?: mixed[]}|string $record 134 * @phpstan-param Level|LevelName|LogLevel::* $level 135 */ 136 public function hasRecord($record, $level): bool 137 { 138 if (is_string($record)) { 139 $record = array('message' => $record); 140 } 141 142 return $this->hasRecordThatPasses(function ($rec) use ($record) { 143 if ($rec['message'] !== $record['message']) { 144 return false; 145 } 146 if (isset($record['context']) && $rec['context'] !== $record['context']) { 147 return false; 148 } 149 150 return true; 151 }, $level); 152 } 153 154 /** 155 * @param string|int $level Logging level value or name 156 * 157 * @phpstan-param Level|LevelName|LogLevel::* $level 158 */ 159 public function hasRecordThatContains(string $message, $level): bool 160 { 161 return $this->hasRecordThatPasses(function ($rec) use ($message) { 162 return strpos($rec['message'], $message) !== false; 163 }, $level); 164 } 165 166 /** 167 * @param string|int $level Logging level value or name 168 * 169 * @phpstan-param Level|LevelName|LogLevel::* $level 170 */ 171 public function hasRecordThatMatches(string $regex, $level): bool 172 { 173 return $this->hasRecordThatPasses(function (array $rec) use ($regex): bool { 174 return preg_match($regex, $rec['message']) > 0; 175 }, $level); 176 } 177 178 /** 179 * @param string|int $level Logging level value or name 180 * @return bool 181 * 182 * @psalm-param callable(Record, int): mixed $predicate 183 * @phpstan-param Level|LevelName|LogLevel::* $level 184 */ 185 public function hasRecordThatPasses(callable $predicate, $level) 186 { 187 $level = Logger::toMonologLevel($level); 188 189 if (!isset($this->recordsByLevel[$level])) { 190 return false; 191 } 192 193 foreach ($this->recordsByLevel[$level] as $i => $rec) { 194 if ($predicate($rec, $i)) { 195 return true; 196 } 197 } 198 199 return false; 200 } 201 202 /** 203 * {@inheritDoc} 204 */ 205 protected function write(array $record): void 206 { 207 $this->recordsByLevel[$record['level']][] = $record; 208 $this->records[] = $record; 209 } 210 211 /** 212 * @param string $method 213 * @param mixed[] $args 214 * @return bool 215 */ 216 public function __call($method, $args) 217 { 218 if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { 219 $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; 220 $level = constant('Monolog\Logger::' . strtoupper($matches[2])); 221 $callback = [$this, $genericMethod]; 222 if (is_callable($callback)) { 223 $args[] = $level; 224 225 return call_user_func_array($callback, $args); 226 } 227 } 228 229 throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); 230 } 231} 232