TestLogger   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 111
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 8
Bugs 1 Features 0
Metric Value
wmc 21
eloc 43
c 8
b 1
f 0
dl 0
loc 111
ccs 49
cts 49
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A hasRecordThatPasses() 0 13 5
A hasRecord() 0 13 4
A __call() 0 14 4
A hasRecordThatContains() 0 5 1
A hasRecords() 0 7 2
A reset() 0 4 1
A hasRecordThatMatches() 0 5 1
A log() 0 14 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ColinODell\PsrTestLogger;
6
7
use Psr\Log\AbstractLogger;
8
use Psr\Log\InvalidArgumentException;
9
10
/**
11
 * Used for testing purposes.
12
 *
13
 * It records all records and gives you access to them for verification.
14
 *
15
 * @method bool hasEmergency(string|array $record)
16
 * @method bool hasAlert(string|array $record)
17
 * @method bool hasCritical(string|array $record)
18
 * @method bool hasError(string|array $record)
19
 * @method bool hasWarning(string|array $record)
20
 * @method bool hasNotice(string|array $record)
21
 * @method bool hasInfo(string|array $record)
22
 * @method bool hasDebug(string|array $record)
23
 * @method bool hasEmergencyRecords()
24
 * @method bool hasAlertRecords()
25
 * @method bool hasCriticalRecords()
26
 * @method bool hasErrorRecords()
27
 * @method bool hasWarningRecords()
28
 * @method bool hasNoticeRecords()
29
 * @method bool hasInfoRecords()
30
 * @method bool hasDebugRecords()
31
 * @method bool hasEmergencyThatContains(string $message)
32
 * @method bool hasAlertThatContains(string $message)
33
 * @method bool hasCriticalThatContains(string $message)
34
 * @method bool hasErrorThatContains(string $message)
35
 * @method bool hasWarningThatContains(string $message)
36
 * @method bool hasNoticeThatContains(string $message)
37
 * @method bool hasInfoThatContains(string $message)
38
 * @method bool hasDebugThatContains(string $message)
39
 * @method bool hasEmergencyThatMatches(string $regex)
40
 * @method bool hasAlertThatMatches(string $regex)
41
 * @method bool hasCriticalThatMatches(string $regex)
42
 * @method bool hasErrorThatMatches(string $regex)
43
 * @method bool hasWarningThatMatches(string $regex)
44
 * @method bool hasNoticeThatMatches(string $regex)
45
 * @method bool hasInfoThatMatches(string $regex)
46
 * @method bool hasDebugThatMatches(string $regex)
47
 * @method bool hasEmergencyThatPasses(callable $predicate)
48
 * @method bool hasAlertThatPasses(callable $predicate)
49
 * @method bool hasCriticalThatPasses(callable $predicate)
50
 * @method bool hasErrorThatPasses(callable $predicate)
51
 * @method bool hasWarningThatPasses(callable $predicate)
52
 * @method bool hasNoticeThatPasses(callable $predicate)
53
 * @method bool hasInfoThatPasses(callable $predicate)
54
 * @method bool hasDebugThatPasses(callable $predicate)
55
 *
56
 * Adapted from psr/log,
57
 * Copyright (c) 2012 PHP Framework Interoperability Group
58
 * Used under the MIT license
59
 */
60
final class TestLogger extends AbstractLogger
61
{
62
    /** @var array<int, array<string, mixed>> */
63
    public array $records = [];
64
65
    /** @var array<string|int, array<int, array<string, mixed>>> */
66
    public array $recordsByLevel = [];
67
68
    /**
69
     * {@inheritDoc}
70
     *
71
     * @param array<array-key, mixed> $context
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
72
     */
73 108
    public function log($level, $message, array $context = []): void
74
    {
75 108
        if (! (\is_string($level) || \is_int($level))) {
76 14
            throw new InvalidArgumentException('Unsupported log level. The psr-testlogger library only supports string and integer log levels; passed ' . \print_r($level, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($level, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
            throw new InvalidArgumentException('Unsupported log level. The psr-testlogger library only supports string and integer log levels; passed ' . /** @scrutinizer ignore-type */ \print_r($level, true));
Loading history...
77
        }
78
79 94
        $record = [
80 94
            'level' => $level,
81 94
            'message' => $message,
82 94
            'context' => $context,
83
        ];
84
85 94
        $this->recordsByLevel[$record['level']][] = $record;
86 94
        $this->records[]                          = $record;
87 94
    }
88
89 94
    public function hasRecords(string|int|null $level = null): bool
90
    {
91 94
        if ($level === null) {
92 10
            return \count($this->records) !== 0;
93
        }
94
95 84
        return isset($this->recordsByLevel[$level]);
96
    }
97
98
    /**
99
     * @param string|array<string, mixed> $record
100
     */
101 20
    public function hasRecord(string|array $record, string|int|null $level = null): bool
102
    {
103 20
        if (\is_string($record)) {
0 ignored issues
show
introduced by
The condition is_string($record) is always false.
Loading history...
104 20
            $record = ['message' => $record];
105
        }
106
107 20
        return $this->hasRecordThatPasses(static function (array $rec) use ($record) {
108 20
            if ((string) $rec['message'] !== (string) $record['message']) {
109 18
                return false;
110
            }
111
112 20
            return ! isset($record['context']) || $rec['context'] === $record['context'];
113 20
        }, $level);
114
    }
115
116 18
    public function hasRecordThatContains(string $message, string|int|null $level = null): bool
117
    {
118 18
        return $this->hasRecordThatPasses(static function (array $rec) use ($message) {
119 18
            return \str_contains((string) $rec['message'], $message);
120 18
        }, $level);
121
    }
122
123 18
    public function hasRecordThatMatches(string $regex, string|int|null $level = null): bool
124
    {
125 18
        return $this->hasRecordThatPasses(static function ($rec) use ($regex) {
126 18
            return \preg_match($regex, (string) $rec['message']) > 0;
127 18
        }, $level);
128
    }
129
130
    /**
131
     * @param callable(array<string, mixed>, int): bool $predicate
132
     */
133 74
    public function hasRecordThatPasses(callable $predicate, string|int|null $level = null): bool
134
    {
135 74
        if (! $this->hasRecords($level)) {
136 72
            return false;
137
        }
138
139 74
        foreach ($level === null ? $this->records : $this->recordsByLevel[$level] as $i => $rec) {
140 74
            if (\call_user_func($predicate, $rec, $i)) {
141 74
                return true;
142
            }
143
        }
144
145 36
        return false;
146
    }
147
148
    /**
149
     * @param array<int, mixed> $args
150
     */
151 84
    public function __call(string $method, array $args): bool
152
    {
153 84
        if (\preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
154 82
            $genericMethod = $matches[1] . ($matches[3] !== 'Records' ? 'Record' : '') . $matches[3];
155 82
            $callable      = [$this, $genericMethod];
156 82
            $level         = \strtolower($matches[2]);
157 82
            if (\is_callable($callable)) {
158 82
                $args[] = $level;
159
160 82
                return \call_user_func_array($callable, $args);
161
            }
162
        }
163
164 2
        throw new \BadMethodCallException('Call to undefined method ' . self::class . '::' . $method . '()');
165
    }
166
167 2
    public function reset(): void
168
    {
169 2
        $this->records        = [];
170 2
        $this->recordsByLevel = [];
171 2
    }
172
}
173