- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
191 lines
5.4 KiB
PHP
191 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Mcp\Shared\Formatters;
|
|
|
|
use App\Framework\Mcp\Core\ValueObjects\OutputFormat;
|
|
|
|
/**
|
|
* Text Output Formatter
|
|
*
|
|
* Formatiert Daten als plain text für menschliches Lesen mit strukturierter Ausgabe
|
|
*/
|
|
final readonly class TextFormatter implements OutputFormatter
|
|
{
|
|
public function format(mixed $data): array
|
|
{
|
|
$normalizedData = $this->normalizeData($data);
|
|
$textContent = $this->convertToText($normalizedData);
|
|
|
|
return [
|
|
'content' => $textContent,
|
|
'format' => $this->getFormat()->value,
|
|
'line_count' => substr_count($textContent, "\n") + 1,
|
|
'character_count' => strlen($textContent),
|
|
'word_count' => str_word_count($textContent),
|
|
];
|
|
}
|
|
|
|
public function supports(OutputFormat $format): bool
|
|
{
|
|
return $format === OutputFormat::TEXT;
|
|
}
|
|
|
|
public function getFormat(): OutputFormat
|
|
{
|
|
return OutputFormat::TEXT;
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return $this->getFormat()->getDescription();
|
|
}
|
|
|
|
private function normalizeData(mixed $data): mixed
|
|
{
|
|
return match (true) {
|
|
is_string($data) => $data,
|
|
is_numeric($data) => (string) $data,
|
|
is_bool($data) => $data ? 'true' : 'false',
|
|
is_null($data) => 'null',
|
|
is_array($data) => $data,
|
|
is_object($data) => (array) $data,
|
|
default => (string) $data
|
|
};
|
|
}
|
|
|
|
private function convertToText(mixed $data, int $depth = 0): string
|
|
{
|
|
if (is_string($data)) {
|
|
return $data;
|
|
}
|
|
|
|
if (is_scalar($data)) {
|
|
return (string) $data;
|
|
}
|
|
|
|
if (is_array($data)) {
|
|
return $this->arrayToText($data, $depth);
|
|
}
|
|
|
|
return $this->objectToText($data, $depth);
|
|
}
|
|
|
|
private function arrayToText(array $data, int $depth = 0): string
|
|
{
|
|
if (empty($data)) {
|
|
return "No data available\n";
|
|
}
|
|
|
|
$lines = [];
|
|
$indent = str_repeat(' ', $depth);
|
|
|
|
// Check if it's a simple list or associative array
|
|
if ($this->isSimpleList($data)) {
|
|
return $this->simpleListToText($data, $indent);
|
|
}
|
|
|
|
// Handle associative arrays or complex structures
|
|
foreach ($data as $key => $value) {
|
|
$formattedKey = $this->formatKey($key);
|
|
|
|
if (is_scalar($value) || is_null($value)) {
|
|
$formattedValue = $this->formatScalarValue($value);
|
|
$lines[] = "{$indent}{$formattedKey}: {$formattedValue}";
|
|
} elseif (is_array($value)) {
|
|
if (empty($value)) {
|
|
$lines[] = "{$indent}{$formattedKey}: (empty)";
|
|
} else {
|
|
$lines[] = "{$indent}{$formattedKey}:";
|
|
$nestedText = $this->arrayToText($value, $depth + 1);
|
|
$lines[] = rtrim($nestedText);
|
|
}
|
|
} else {
|
|
$lines[] = "{$indent}{$formattedKey}: " . $this->convertToText($value, $depth + 1);
|
|
}
|
|
}
|
|
|
|
return implode("\n", $lines) . "\n";
|
|
}
|
|
|
|
private function objectToText(mixed $data, int $depth = 0): string
|
|
{
|
|
$className = is_object($data) ? get_class($data) : 'Unknown';
|
|
$properties = is_object($data) ? (array) $data : [];
|
|
|
|
$indent = str_repeat(' ', $depth);
|
|
$lines = ["{$indent}Object: {$className}"];
|
|
|
|
if (! empty($properties)) {
|
|
foreach ($properties as $key => $value) {
|
|
$cleanKey = ltrim($key, "\0*\0");
|
|
$formattedValue = $this->convertToText($value, $depth + 1);
|
|
$lines[] = "{$indent} {$cleanKey}: {$formattedValue}";
|
|
}
|
|
}
|
|
|
|
return implode("\n", $lines) . "\n";
|
|
}
|
|
|
|
private function isSimpleList(array $data): bool
|
|
{
|
|
if (empty($data)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if all keys are sequential integers starting from 0
|
|
$keys = array_keys($data);
|
|
$expectedKeys = range(0, count($data) - 1);
|
|
|
|
if ($keys !== $expectedKeys) {
|
|
return false;
|
|
}
|
|
|
|
// Check if all values are scalar
|
|
foreach ($data as $value) {
|
|
if (! is_scalar($value) && ! is_null($value)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function simpleListToText(array $data, string $indent): string
|
|
{
|
|
$lines = [];
|
|
|
|
foreach ($data as $index => $value) {
|
|
$formattedValue = $this->formatScalarValue($value);
|
|
$lines[] = "{$indent}- {$formattedValue}";
|
|
}
|
|
|
|
return implode("\n", $lines) . "\n";
|
|
}
|
|
|
|
private function formatKey(string|int $key): string
|
|
{
|
|
if (is_numeric($key)) {
|
|
return "Item {$key}";
|
|
}
|
|
|
|
// Convert snake_case or camelCase to readable format
|
|
$formatted = str_replace(['_', '-'], ' ', (string) $key);
|
|
$formatted = preg_replace('/([a-z])([A-Z])/', '$1 $2', $formatted);
|
|
|
|
return ucwords($formatted);
|
|
}
|
|
|
|
private function formatScalarValue(mixed $value): string
|
|
{
|
|
return match (true) {
|
|
is_null($value) => '(null)',
|
|
is_bool($value) => $value ? 'Yes' : 'No',
|
|
is_string($value) => empty($value) ? '(empty)' : $value,
|
|
is_numeric($value) => (string) $value,
|
|
default => (string) $value
|
|
};
|
|
}
|
|
}
|