Files
michaelschiemer/src/Framework/View/Table/Table.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

260 lines
7.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\View\Table;
final readonly class Table
{
/**
* @param TableColumn[] $columns
* @param TableRow[] $rows
*/
public function __construct(
public array $columns,
public array $rows,
public ?string $cssClass = null,
public ?TableOptions $options = null,
public ?string $id = null
) {
}
public function render(): string
{
$options = $this->options ?? new TableOptions();
$classString = $this->buildClassString($options);
$attributesString = $this->buildAttributesString($options);
$idString = $this->id ? " id=\"{$this->id}\"" : '';
$thead = $this->renderThead();
$tbody = $this->renderTbody($options);
return "<table{$classString}{$idString}{$attributesString}>{$thead}{$tbody}</table>";
}
private function renderThead(): string
{
$headerCells = array_map(fn (TableColumn $column) => $column->renderHeader(), $this->columns);
return "<thead><tr>" . implode('', $headerCells) . "</tr></thead>";
}
private function renderTbody(TableOptions $options): string
{
if (empty($this->rows)) {
$colspan = count($this->columns);
$emptyMessage = $options->emptyMessage ?? 'No data available';
return "<tbody><tr><td colspan=\"{$colspan}\" class=\"text-center empty-message\">{$emptyMessage}</td></tr></tbody>";
}
$rowsHtml = array_map(fn (TableRow $row) => $row->render(), $this->rows);
return "<tbody>" . implode('', $rowsHtml) . "</tbody>";
}
private function buildClassString(TableOptions $options): string
{
$classes = [];
if ($this->cssClass) {
$classes[] = $this->cssClass;
}
if ($options->striped) {
$classes[] = 'table-striped';
}
if ($options->bordered) {
$classes[] = 'table-bordered';
}
if ($options->hover) {
$classes[] = 'table-hover';
}
if ($options->responsive) {
$classes[] = 'table-responsive';
}
return $classes ? ' class="' . implode(' ', $classes) . '"' : '';
}
private function buildAttributesString(TableOptions $options): string
{
if (! $options->tableAttributes) {
return '';
}
$parts = [];
foreach ($options->tableAttributes as $key => $value) {
$parts[] = $key . '="' . htmlspecialchars((string) $value, ENT_QUOTES) . '"';
}
return $parts ? ' ' . implode(' ', $parts) : '';
}
/**
* Create table from array data with column definitions
*/
public static function fromArray(array $data, array $columnDefs, ?TableOptions $options = null): self
{
$columns = [];
$rows = [];
// Build columns
foreach ($columnDefs as $key => $def) {
if (is_string($def)) {
// Simple string header
$columns[] = TableColumn::text($key, $def);
} elseif ($def instanceof TableColumn) {
// Already a TableColumn
$columns[] = $def;
} elseif (is_array($def)) {
// Array definition
$columns[] = new TableColumn(
key: $key,
header: $def['header'] ?? $key,
cssClass: $def['class'] ?? null,
formatter: $def['formatter'] ?? null,
sortable: $def['sortable'] ?? false,
width: $def['width'] ?? null,
defaultType: $def['type'] ?? null
);
}
}
// Build rows
foreach ($data as $rowData) {
$rows[] = TableRow::fromData($rowData, $columns);
}
return new self($columns, $rows, null, $options);
}
/**
* Create simple table from 2D array
*/
public static function fromSimpleArray(array $headers, array $data, ?TableOptions $options = null): self
{
$columns = array_map(
fn ($header, $index) => TableColumn::text((string) $index, $header),
$headers,
array_keys($headers)
);
$rows = array_map(
fn ($rowData) => TableRow::fromValues($rowData),
$data
);
return new self($columns, $rows, null, $options);
}
/**
* Create environment variables table
*/
public static function forEnvironmentVars(array $env): self
{
$columns = [
TableColumn::text('key', 'Variable', 'env-key'),
TableColumn::text('value', 'Wert', 'env-value'),
];
$rows = [];
foreach ($env as $key => $value) {
// Mask sensitive values - ensure key is string
$displayValue = self::maskSensitiveValue((string) $key, $value);
$rows[] = TableRow::fromData([
'key' => (string) $key,
'value' => $displayValue,
], $columns, 'env-row');
}
return new self(
columns: $columns,
rows: $rows,
cssClass: 'admin-table',
options: TableOptions::admin(),
id: 'envTable'
);
}
/**
* Create migrations table
*/
public static function forMigrations(array $migrations): self
{
$columns = [
TableColumn::withFormatter(
'status',
'Status',
Formatters\StatusFormatter::withBadges(),
'status-col'
),
TableColumn::text('version', 'Version', 'version-col'),
TableColumn::text('description', 'Description', 'description-col'),
TableColumn::withFormatter(
'applied',
'Applied',
Formatters\BooleanFormatter::germanWithBadges(),
'applied-col'
),
];
$rows = [];
foreach ($migrations as $migration) {
$statusData = [
'status_icon' => $migration['status_icon'] ?? '',
'status_text' => $migration['status_text'] ?? '',
'status_class' => $migration['status_class'] ?? 'secondary',
];
$rows[] = TableRow::fromData([
'status' => $statusData,
'version' => $migration['version'] ?? '',
'description' => $migration['description'] ?? '',
'applied' => $migration['applied'] ?? false,
], $columns, 'migration-row');
}
return new self(
columns: $columns,
rows: $rows,
cssClass: 'admin-table',
options: TableOptions::admin(),
id: 'migrationsTable'
);
}
private static function maskSensitiveValue(string $key, mixed $value): string
{
$sensitiveKeys = ['password', 'secret', 'key', 'token', 'auth'];
foreach ($sensitiveKeys as $sensitiveKey) {
if (stripos($key, $sensitiveKey) !== false) {
return '********';
}
}
// Handle arrays and objects properly
if (is_array($value)) {
$encoded = json_encode($value, JSON_PRETTY_PRINT);
return $encoded ?: '[Array]';
}
if (is_object($value)) {
if (method_exists($value, '__toString')) {
return (string) $value;
}
return get_class($value);
}
return (string) $value;
}
}