1: <?php declare(strict_types=1);
2:
3: namespace Salient\Sync\Support;
4:
5: use Salient\Collection\AbstractTypedCollection;
6: use Salient\Console\ConsoleFormatter as Formatter;
7: use Salient\Contract\Console\ConsoleMessageType as MessageType;
8: use Salient\Contract\Console\ConsoleWriterInterface;
9: use Salient\Contract\Core\MessageLevel as Level;
10: use Salient\Contract\Sync\SyncErrorCollectionInterface;
11: use Salient\Contract\Sync\SyncErrorInterface;
12: use Salient\Contract\Sync\SyncErrorType as ErrorType;
13: use Salient\Core\Facade\Console;
14: use Salient\Utility\Arr;
15: use Salient\Utility\Inflect;
16: use Salient\Utility\Reflect;
17:
18: /**
19: * @extends AbstractTypedCollection<int,SyncErrorInterface>
20: */
21: final class SyncErrorCollection extends AbstractTypedCollection implements SyncErrorCollectionInterface
22: {
23: private int $ErrorCount = 0;
24: private int $WarningCount = 0;
25:
26: /**
27: * @inheritDoc
28: */
29: public function getErrorCount(): int
30: {
31: return $this->ErrorCount;
32: }
33:
34: /**
35: * @inheritDoc
36: */
37: public function getWarningCount(): int
38: {
39: return $this->WarningCount;
40: }
41:
42: /**
43: * @inheritDoc
44: */
45: public function getSummary(): array
46: {
47: foreach ($this->Items as $error) {
48: $code = $error->getCode();
49: $format = $error->getFormat();
50: $key = "$code.$format";
51:
52: $summary[$key] ??= [
53: 'code' => $code,
54: 'title' => Reflect::getConstantName(ErrorType::class, $error->getType()),
55: 'detail' => $format,
56: 'meta' => [
57: 'level' => Reflect::getConstantName(Level::class, $error->getLevel()),
58: 'count' => 0,
59: 'seen' => 0,
60: ],
61: ];
62:
63: /** @var mixed[]|object|int|float|string|bool|null */
64: $values = Arr::unwrap($error->getValues());
65: $summary[$key]['meta']['values'][] = $values;
66: $summary[$key]['meta']['count']++;
67: $summary[$key]['meta']['seen'] += $error->getCount();
68: }
69:
70: return array_values(Arr::sortByKey($summary ?? []));
71: }
72:
73: /**
74: * @inheritDoc
75: */
76: public function getSummaryText(): string
77: {
78: return $this->doGetSummaryText(false);
79: }
80:
81: private function doGetSummaryText(bool $withMarkup): string
82: {
83: $format = $withMarkup
84: ? "~~{~~_%d_~~}~~ ___%s___ ~~[~~__%s__~~]~~ ~~(~~_'%s'_~~)~~:\n %s"
85: : "{%d} %s [%s] ('%s'):\n %s";
86:
87: foreach ($this->getSummary() as $error) {
88: $values = Arr::toScalars($error['meta']['values']);
89:
90: if ($withMarkup) {
91: foreach ($values as $key => $value) {
92: $values[$key] = Formatter::escapeTags((string) $value);
93: }
94: }
95:
96: $lines[] = sprintf(
97: $format,
98: $error['meta']['seen'],
99: $error['title'],
100: $error['meta']['level'],
101: $error['detail'],
102: implode("\n ", $values),
103: );
104: }
105:
106: return implode("\n", $lines ?? []);
107: }
108:
109: /**
110: * @inheritDoc
111: */
112: public function reportErrors(
113: ?ConsoleWriterInterface $writer = null,
114: string $successText = 'No sync errors recorded'
115: ): void {
116: $writer ??= Console::getInstance();
117:
118: if (!$this->ErrorCount && !$this->WarningCount) {
119: $writer->info($successText);
120: return;
121: }
122:
123: $level = $this->ErrorCount
124: ? Level::ERROR
125: : Level::WARNING;
126:
127: $writer->message(
128: Inflect::format(
129: $this->ErrorCount,
130: '{{#}} sync {{#:error}}%s recorded:',
131: $this->WarningCount
132: ? Inflect::format($this->WarningCount, ' and {{#}} {{#:warning}}')
133: : ''
134: ),
135: null,
136: $level,
137: MessageType::STANDARD,
138: );
139:
140: $writer->print(
141: $this->doGetSummaryText(true),
142: $level,
143: MessageType::UNFORMATTED,
144: );
145: }
146:
147: /**
148: * @inheritDoc
149: */
150: public function __toString(): string
151: {
152: return $this->getSummaryText();
153: }
154:
155: /**
156: * @return array<array{code:string,title:string,detail:string,meta:array{level:string,count:int,seen:int,values:list<mixed[]|object|int|float|string|bool|null>}}>
157: */
158: public function jsonSerialize(): array
159: {
160: return $this->getSummary();
161: }
162:
163: /**
164: * @inheritDoc
165: */
166: protected function handleItemsReplaced(): void
167: {
168: $errors = 0;
169: $warnings = 0;
170: foreach ($this->Items as $error) {
171: switch ($error->getLevel()) {
172: case Level::EMERGENCY:
173: case Level::ALERT:
174: case Level::CRITICAL:
175: case Level::ERROR:
176: $errors++;
177: break;
178: case Level::WARNING:
179: $warnings++;
180: break;
181: }
182: }
183:
184: $this->ErrorCount = $errors;
185: $this->WarningCount = $warnings;
186: }
187: }
188: