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