1: <?php declare(strict_types=1);
2:
3: namespace Salient\Testing\Console;
4:
5: use Salient\Console\ConsoleFormatter as Formatter;
6: use Salient\Contract\Console\ConsoleFormatterInterface as FormatterInterface;
7: use Salient\Contract\Console\ConsoleTargetStreamInterface;
8: use Salient\Contract\Core\MessageLevel as Level;
9: use Salient\Utility\File;
10: use LogicException;
11:
12: /**
13: * @api
14: */
15: final class MockTarget implements ConsoleTargetStreamInterface
16: {
17: private bool $IsStdout;
18: private bool $IsStderr;
19: private bool $IsTty;
20: private ?int $Width;
21: /** @var resource|null */
22: private $Stream;
23: private FormatterInterface $Formatter;
24: /** @var array<array{Level::*,string,2?:array<string,mixed>}> */
25: private array $Messages = [];
26: private bool $IsValid = true;
27:
28: /**
29: * @param resource|null $stream Console messages are also written to
30: * `$stream` if given.
31: */
32: public function __construct(
33: $stream = null,
34: bool $isStdout = true,
35: bool $isStderr = true,
36: bool $isTty = true,
37: ?int $width = 80,
38: ?FormatterInterface $formatter = null
39: ) {
40: $this->IsStdout = $isStdout;
41: $this->IsStderr = $isStderr;
42: $this->IsTty = $isTty;
43: $this->Width = $width;
44: $this->Stream = $stream;
45: $this->Formatter = $formatter ?? new Formatter(null, null, fn() => $this->Width);
46: }
47:
48: /**
49: * @inheritDoc
50: */
51: public function isStdout(): bool
52: {
53: return $this->IsStdout;
54: }
55:
56: /**
57: * @inheritDoc
58: */
59: public function isStderr(): bool
60: {
61: return $this->IsStderr;
62: }
63:
64: /**
65: * @inheritDoc
66: */
67: public function isTty(): bool
68: {
69: return $this->IsTty;
70: }
71:
72: /**
73: * @inheritDoc
74: */
75: public function getEol(): string
76: {
77: return "\n";
78: }
79:
80: /**
81: * @inheritDoc
82: */
83: public function close(): void
84: {
85: if (!$this->IsValid) {
86: return;
87: }
88:
89: $this->IsStdout = false;
90: $this->IsStderr = false;
91: $this->IsTty = false;
92: $this->Width = null;
93: $this->Stream = null;
94: unset($this->Formatter);
95:
96: $this->IsValid = false;
97: }
98:
99: /**
100: * @inheritDoc
101: */
102: public function reopen(): void {}
103:
104: /**
105: * @inheritDoc
106: */
107: public function getFormatter(): FormatterInterface
108: {
109: $this->assertIsValid();
110:
111: return $this->Formatter;
112: }
113:
114: /**
115: * @inheritDoc
116: */
117: public function getWidth(): ?int
118: {
119: $this->assertIsValid();
120:
121: return $this->Width;
122: }
123:
124: /**
125: * @inheritDoc
126: */
127: public function write($level, string $message, array $context = []): void
128: {
129: $this->assertIsValid();
130:
131: if ($this->Stream) {
132: $suffix = $message === '' || $message[-1] !== "\r"
133: ? "\n"
134: : '';
135: File::write($this->Stream, $message . $suffix);
136: }
137:
138: $message = [$level, $message];
139: if ($context) {
140: $message[] = $context;
141: }
142: $this->Messages[] = $message;
143: }
144:
145: /**
146: * Get messages written to the target and flush its message cache
147: *
148: * @return array<array{Level::*,string,2?:array<string,mixed>}>
149: */
150: public function getMessages(): array
151: {
152: $messages = $this->Messages;
153: $this->Messages = [];
154: return $messages;
155: }
156:
157: private function assertIsValid(): void
158: {
159: if (!$this->IsValid) {
160: throw new LogicException('Target is closed');
161: }
162: }
163: }
164: