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 "{$thead}{$tbody}"; } private function renderThead(): string { $headerCells = array_map(fn (TableColumn $column) => $column->renderHeader(), $this->columns); return "" . implode('', $headerCells) . ""; } private function renderTbody(TableOptions $options): string { if (empty($this->rows)) { $colspan = count($this->columns); $emptyMessage = $options->emptyMessage ?? 'No data available'; return "{$emptyMessage}"; } $rowsHtml = array_map(fn (TableRow $row) => $row->render(), $this->rows); return "" . implode('', $rowsHtml) . ""; } 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; } }