1: <?php declare(strict_types=1);
2:
3: namespace Salient\Testing\Console;
4:
5: use Salient\Console\Format\Formatter;
6: use Salient\Contract\Console\Format\FormatterInterface;
7: use Salient\Contract\Console\Target\StreamTargetInterface;
8: use Salient\Contract\Console\ConsoleInterface as Console;
9: use Salient\Utility\File;
10: use LogicException;
11:
12: /**
13: * @api
14: */
15: final class MockTarget implements StreamTargetInterface
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{Console::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: * @internal
50: */
51: public function __destruct()
52: {
53: $this->close();
54: }
55:
56: /**
57: * @inheritDoc
58: */
59: public function isStdout(): bool
60: {
61: return $this->IsStdout;
62: }
63:
64: /**
65: * @inheritDoc
66: */
67: public function isStderr(): bool
68: {
69: return $this->IsStderr;
70: }
71:
72: /**
73: * @inheritDoc
74: */
75: public function isTty(): bool
76: {
77: return $this->IsTty;
78: }
79:
80: /**
81: * @inheritDoc
82: */
83: public function getUri(): ?string
84: {
85: return $this->Stream ? File::getStreamUri($this->Stream) : null;
86: }
87:
88: /**
89: * @inheritDoc
90: */
91: public function reopen(): void {}
92:
93: /**
94: * @inheritDoc
95: */
96: public function getFormatter(): FormatterInterface
97: {
98: $this->assertIsValid();
99:
100: return $this->Formatter;
101: }
102:
103: /**
104: * @inheritDoc
105: */
106: public function getWidth(): ?int
107: {
108: $this->assertIsValid();
109:
110: return $this->Width;
111: }
112:
113: /**
114: * @inheritDoc
115: */
116: public function write(int $level, string $message, array $context = []): void
117: {
118: $this->assertIsValid();
119:
120: if ($this->Stream) {
121: $suffix = $message !== '' && $message[-1] === "\r"
122: ? ''
123: : "\n";
124: File::writeAll($this->Stream, $message . $suffix);
125: }
126:
127: $message = [$level, $message];
128: if ($context) {
129: $message[] = $context;
130: }
131: $this->Messages[] = $message;
132: }
133:
134: /**
135: * @inheritDoc
136: */
137: public function close(): void
138: {
139: if (!$this->IsValid) {
140: return;
141: }
142:
143: $this->IsStdout = false;
144: $this->IsStderr = false;
145: $this->IsTty = false;
146: $this->Stream = null;
147: $this->IsValid = false;
148: }
149:
150: /**
151: * Get messages written to the target and flush its message cache
152: *
153: * @return array<array{Console::LEVEL_*,string,2?:array<string,mixed>}>
154: */
155: public function getMessages(): array
156: {
157: $messages = $this->Messages;
158: $this->Messages = [];
159: return $messages;
160: }
161:
162: private function assertIsValid(): void
163: {
164: if (!$this->IsValid) {
165: throw new LogicException('Target is closed');
166: }
167: }
168: }
169: