Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Table.php

Zuletzt modifiziert: 01.05.2026, 11:27 - Dateigröße: 19.43 KiB


 001  <?php
 002   
 003  /*
 004   * This file is part of the Symfony package.
 005   *
 006   * (c) Fabien Potencier <fabien@symfony.com>
 007   *
 008   * For the full copyright and license information, please view the LICENSE
 009   * file that was distributed with this source code.
 010   */
 011   
 012  namespace Symfony\Component\Console\Helper;
 013   
 014  use Symfony\Component\Console\Exception\InvalidArgumentException;
 015  use Symfony\Component\Console\Output\OutputInterface;
 016   
 017  /**
 018   * Provides helpers to display a table.
 019   *
 020   * @author Fabien Potencier <fabien@symfony.com>
 021   * @author Саша Стаменковић <umpirsky@gmail.com>
 022   * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 023   * @author Max Grigorian <maxakawizard@gmail.com>
 024   */
 025  class Table
 026  {
 027      /**
 028       * Table headers.
 029       */
 030      private $headers = [];
 031   
 032      /**
 033       * Table rows.
 034       */
 035      private $rows = [];
 036   
 037      /**
 038       * Column widths cache.
 039       */
 040      private $effectiveColumnWidths = [];
 041   
 042      /**
 043       * Number of columns cache.
 044       *
 045       * @var int
 046       */
 047      private $numberOfColumns;
 048   
 049      /**
 050       * @var OutputInterface
 051       */
 052      private $output;
 053   
 054      /**
 055       * @var TableStyle
 056       */
 057      private $style;
 058   
 059      /**
 060       * @var array
 061       */
 062      private $columnStyles = [];
 063   
 064      /**
 065       * User set column widths.
 066       *
 067       * @var array
 068       */
 069      private $columnWidths = [];
 070   
 071      private static $styles;
 072   
 073      public function __construct(OutputInterface $output)
 074      {
 075          $this->output = $output;
 076   
 077          if (!self::$styles) {
 078              self::$styles = self::initStyles();
 079          }
 080   
 081          $this->setStyle('default');
 082      }
 083   
 084      /**
 085       * Sets a style definition.
 086       *
 087       * @param string     $name  The style name
 088       * @param TableStyle $style A TableStyle instance
 089       */
 090      public static function setStyleDefinition($name, TableStyle $style)
 091      {
 092          if (!self::$styles) {
 093              self::$styles = self::initStyles();
 094          }
 095   
 096          self::$styles[$name] = $style;
 097      }
 098   
 099      /**
 100       * Gets a style definition by name.
 101       *
 102       * @param string $name The style name
 103       *
 104       * @return TableStyle
 105       */
 106      public static function getStyleDefinition($name)
 107      {
 108          if (!self::$styles) {
 109              self::$styles = self::initStyles();
 110          }
 111   
 112          if (isset(self::$styles[$name])) {
 113              return self::$styles[$name];
 114          }
 115   
 116          throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
 117      }
 118   
 119      /**
 120       * Sets table style.
 121       *
 122       * @param TableStyle|string $name The style name or a TableStyle instance
 123       *
 124       * @return $this
 125       */
 126      public function setStyle($name)
 127      {
 128          $this->style = $this->resolveStyle($name);
 129   
 130          return $this;
 131      }
 132   
 133      /**
 134       * Gets the current table style.
 135       *
 136       * @return TableStyle
 137       */
 138      public function getStyle()
 139      {
 140          return $this->style;
 141      }
 142   
 143      /**
 144       * Sets table column style.
 145       *
 146       * @param int               $columnIndex Column index
 147       * @param TableStyle|string $name        The style name or a TableStyle instance
 148       *
 149       * @return $this
 150       */
 151      public function setColumnStyle($columnIndex, $name)
 152      {
 153          $columnIndex = (int) $columnIndex;
 154   
 155          $this->columnStyles[$columnIndex] = $this->resolveStyle($name);
 156   
 157          return $this;
 158      }
 159   
 160      /**
 161       * Gets the current style for a column.
 162       *
 163       * If style was not set, it returns the global table style.
 164       *
 165       * @param int $columnIndex Column index
 166       *
 167       * @return TableStyle
 168       */
 169      public function getColumnStyle($columnIndex)
 170      {
 171          if (isset($this->columnStyles[$columnIndex])) {
 172              return $this->columnStyles[$columnIndex];
 173          }
 174   
 175          return $this->getStyle();
 176      }
 177   
 178      /**
 179       * Sets the minimum width of a column.
 180       *
 181       * @param int $columnIndex Column index
 182       * @param int $width       Minimum column width in characters
 183       *
 184       * @return $this
 185       */
 186      public function setColumnWidth($columnIndex, $width)
 187      {
 188          $this->columnWidths[(int) $columnIndex] = (int) $width;
 189   
 190          return $this;
 191      }
 192   
 193      /**
 194       * Sets the minimum width of all columns.
 195       *
 196       * @return $this
 197       */
 198      public function setColumnWidths(array $widths)
 199      {
 200          $this->columnWidths = [];
 201          foreach ($widths as $index => $width) {
 202              $this->setColumnWidth($index, $width);
 203          }
 204   
 205          return $this;
 206      }
 207   
 208      public function setHeaders(array $headers)
 209      {
 210          $headers = array_values($headers);
 211          if (!empty($headers) && !\is_array($headers[0])) {
 212              $headers = [$headers];
 213          }
 214   
 215          $this->headers = $headers;
 216   
 217          return $this;
 218      }
 219   
 220      public function setRows(array $rows)
 221      {
 222          $this->rows = [];
 223   
 224          return $this->addRows($rows);
 225      }
 226   
 227      public function addRows(array $rows)
 228      {
 229          foreach ($rows as $row) {
 230              $this->addRow($row);
 231          }
 232   
 233          return $this;
 234      }
 235   
 236      public function addRow($row)
 237      {
 238          if ($row instanceof TableSeparator) {
 239              $this->rows[] = $row;
 240   
 241              return $this;
 242          }
 243   
 244          if (!\is_array($row)) {
 245              throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
 246          }
 247   
 248          $this->rows[] = array_values($row);
 249   
 250          return $this;
 251      }
 252   
 253      public function setRow($column, array $row)
 254      {
 255          $this->rows[$column] = $row;
 256   
 257          return $this;
 258      }
 259   
 260      /**
 261       * Renders table to output.
 262       *
 263       * Example:
 264       *
 265       *     +---------------+-----------------------+------------------+
 266       *     | ISBN          | Title                 | Author           |
 267       *     +---------------+-----------------------+------------------+
 268       *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
 269       *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
 270       *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
 271       *     +---------------+-----------------------+------------------+
 272       */
 273      public function render()
 274      {
 275          $this->calculateNumberOfColumns();
 276          $rows = $this->buildTableRows($this->rows);
 277          $headers = $this->buildTableRows($this->headers);
 278   
 279          $this->calculateColumnsWidth(array_merge($headers, $rows));
 280   
 281          $this->renderRowSeparator();
 282          if (!empty($headers)) {
 283              foreach ($headers as $header) {
 284                  $this->renderRow($header, $this->style->getCellHeaderFormat());
 285                  $this->renderRowSeparator();
 286              }
 287          }
 288          foreach ($rows as $row) {
 289              if ($row instanceof TableSeparator) {
 290                  $this->renderRowSeparator();
 291              } else {
 292                  $this->renderRow($row, $this->style->getCellRowFormat());
 293              }
 294          }
 295          if (!empty($rows)) {
 296              $this->renderRowSeparator();
 297          }
 298   
 299          $this->cleanup();
 300      }
 301   
 302      /**
 303       * Renders horizontal header separator.
 304       *
 305       * Example:
 306       *
 307       *     +-----+-----------+-------+
 308       */
 309      private function renderRowSeparator()
 310      {
 311          if (0 === $count = $this->numberOfColumns) {
 312              return;
 313          }
 314   
 315          if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
 316              return;
 317          }
 318   
 319          $markup = $this->style->getCrossingChar();
 320          for ($column = 0; $column < $count; ++$column) {
 321              $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar();
 322          }
 323   
 324          $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
 325      }
 326   
 327      /**
 328       * Renders vertical column separator.
 329       */
 330      private function renderColumnSeparator()
 331      {
 332          return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
 333      }
 334   
 335      /**
 336       * Renders table row.
 337       *
 338       * Example:
 339       *
 340       *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
 341       *
 342       * @param string $cellFormat
 343       */
 344      private function renderRow(array $row, $cellFormat)
 345      {
 346          if (empty($row)) {
 347              return;
 348          }
 349   
 350          $rowContent = $this->renderColumnSeparator();
 351          foreach ($this->getRowColumns($row) as $column) {
 352              $rowContent .= $this->renderCell($row, $column, $cellFormat);
 353              $rowContent .= $this->renderColumnSeparator();
 354          }
 355          $this->output->writeln($rowContent);
 356      }
 357   
 358      /**
 359       * Renders table cell with padding.
 360       *
 361       * @param int    $column
 362       * @param string $cellFormat
 363       */
 364      private function renderCell(array $row, $column, $cellFormat)
 365      {
 366          $cell = isset($row[$column]) ? $row[$column] : '';
 367          $width = $this->effectiveColumnWidths[$column];
 368          if ($cell instanceof TableCell && $cell->getColspan() > 1) {
 369              // add the width of the following columns(numbers of colspan).
 370              foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
 371                  $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
 372              }
 373          }
 374   
 375          // str_pad won't work properly with multi-byte strings, we need to fix the padding
 376          if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
 377              $width += \strlen($cell) - mb_strwidth($cell, $encoding);
 378          }
 379   
 380          $style = $this->getColumnStyle($column);
 381   
 382          if ($cell instanceof TableSeparator) {
 383              return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width));
 384          }
 385   
 386          $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
 387          $content = sprintf($style->getCellRowContentFormat(), $cell);
 388   
 389          return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType()));
 390      }
 391   
 392      /**
 393       * Calculate number of columns for this table.
 394       */
 395      private function calculateNumberOfColumns()
 396      {
 397          if (null !== $this->numberOfColumns) {
 398              return;
 399          }
 400   
 401          $columns = [0];
 402          foreach (array_merge($this->headers, $this->rows) as $row) {
 403              if ($row instanceof TableSeparator) {
 404                  continue;
 405              }
 406   
 407              $columns[] = $this->getNumberOfColumns($row);
 408          }
 409   
 410          $this->numberOfColumns = max($columns);
 411      }
 412   
 413      private function buildTableRows($rows)
 414      {
 415          $unmergedRows = [];
 416          for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
 417              $rows = $this->fillNextRows($rows, $rowKey);
 418   
 419              // Remove any new line breaks and replace it with a new line
 420              foreach ($rows[$rowKey] as $column => $cell) {
 421                  if (!strstr($cell, "\n")) {
 422                      continue;
 423                  }
 424                  $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
 425                  foreach ($lines as $lineKey => $line) {
 426                      if ($cell instanceof TableCell) {
 427                          $line = new TableCell($line, ['colspan' => $cell->getColspan()]);
 428                      }
 429                      if (0 === $lineKey) {
 430                          $rows[$rowKey][$column] = $line;
 431                      } else {
 432                          $unmergedRows[$rowKey][$lineKey][$column] = $line;
 433                      }
 434                  }
 435              }
 436          }
 437   
 438          $tableRows = [];
 439          foreach ($rows as $rowKey => $row) {
 440              $tableRows[] = $this->fillCells($row);
 441              if (isset($unmergedRows[$rowKey])) {
 442                  $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]);
 443              }
 444          }
 445   
 446          return $tableRows;
 447      }
 448   
 449      /**
 450       * fill rows that contains rowspan > 1.
 451       *
 452       * @param int $line
 453       *
 454       * @return array
 455       *
 456       * @throws InvalidArgumentException
 457       */
 458      private function fillNextRows(array $rows, $line)
 459      {
 460          $unmergedRows = [];
 461          foreach ($rows[$line] as $column => $cell) {
 462              if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
 463                  throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell)));
 464              }
 465              if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
 466                  $nbLines = $cell->getRowspan() - 1;
 467                  $lines = [$cell];
 468                  if (strstr($cell, "\n")) {
 469                      $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
 470                      $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
 471   
 472                      $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]);
 473                      unset($lines[0]);
 474                  }
 475   
 476                  // create a two dimensional array (rowspan x colspan)
 477                  $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
 478                  foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
 479                      $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
 480                      $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]);
 481                      if ($nbLines === $unmergedRowKey - $line) {
 482                          break;
 483                      }
 484                  }
 485              }
 486          }
 487   
 488          foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
 489              // we need to know if $unmergedRow will be merged or inserted into $rows
 490              if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
 491                  foreach ($unmergedRow as $cellKey => $cell) {
 492                      // insert cell into row at cellKey position
 493                      array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
 494                  }
 495              } else {
 496                  $row = $this->copyRow($rows, $unmergedRowKey - 1);
 497                  foreach ($unmergedRow as $column => $cell) {
 498                      if (!empty($cell)) {
 499                          $row[$column] = $unmergedRow[$column];
 500                      }
 501                  }
 502                  array_splice($rows, $unmergedRowKey, 0, [$row]);
 503              }
 504          }
 505   
 506          return $rows;
 507      }
 508   
 509      /**
 510       * fill cells for a row that contains colspan > 1.
 511       *
 512       * @return array
 513       */
 514      private function fillCells($row)
 515      {
 516          $newRow = [];
 517          foreach ($row as $column => $cell) {
 518              $newRow[] = $cell;
 519              if ($cell instanceof TableCell && $cell->getColspan() > 1) {
 520                  foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
 521                      // insert empty value at column position
 522                      $newRow[] = '';
 523                  }
 524              }
 525          }
 526   
 527          return $newRow ?: $row;
 528      }
 529   
 530      /**
 531       * @param int $line
 532       *
 533       * @return array
 534       */
 535      private function copyRow(array $rows, $line)
 536      {
 537          $row = $rows[$line];
 538          foreach ($row as $cellKey => $cellValue) {
 539              $row[$cellKey] = '';
 540              if ($cellValue instanceof TableCell) {
 541                  $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
 542              }
 543          }
 544   
 545          return $row;
 546      }
 547   
 548      /**
 549       * Gets number of columns by row.
 550       *
 551       * @return int
 552       */
 553      private function getNumberOfColumns(array $row)
 554      {
 555          $columns = \count($row);
 556          foreach ($row as $column) {
 557              $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
 558          }
 559   
 560          return $columns;
 561      }
 562   
 563      /**
 564       * Gets list of columns for the given row.
 565       *
 566       * @return array
 567       */
 568      private function getRowColumns(array $row)
 569      {
 570          $columns = range(0, $this->numberOfColumns - 1);
 571          foreach ($row as $cellKey => $cell) {
 572              if ($cell instanceof TableCell && $cell->getColspan() > 1) {
 573                  // exclude grouped columns.
 574                  $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
 575              }
 576          }
 577   
 578          return $columns;
 579      }
 580   
 581      /**
 582       * Calculates columns widths.
 583       */
 584      private function calculateColumnsWidth(array $rows)
 585      {
 586          for ($column = 0; $column < $this->numberOfColumns; ++$column) {
 587              $lengths = [];
 588              foreach ($rows as $row) {
 589                  if ($row instanceof TableSeparator) {
 590                      continue;
 591                  }
 592   
 593                  foreach ($row as $i => $cell) {
 594                      if ($cell instanceof TableCell) {
 595                          $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
 596                          $textLength = Helper::strlen($textContent);
 597                          if ($textLength > 0) {
 598                              $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
 599                              foreach ($contentColumns as $position => $content) {
 600                                  $row[$i + $position] = $content;
 601                              }
 602                          }
 603                      }
 604                  }
 605   
 606                  $lengths[] = $this->getCellWidth($row, $column);
 607              }
 608   
 609              $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
 610          }
 611      }
 612   
 613      /**
 614       * Gets column width.
 615       *
 616       * @return int
 617       */
 618      private function getColumnSeparatorWidth()
 619      {
 620          return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
 621      }
 622   
 623      /**
 624       * Gets cell width.
 625       *
 626       * @param int $column
 627       *
 628       * @return int
 629       */
 630      private function getCellWidth(array $row, $column)
 631      {
 632          $cellWidth = 0;
 633   
 634          if (isset($row[$column])) {
 635              $cell = $row[$column];
 636              $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
 637          }
 638   
 639          $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
 640   
 641          return max($cellWidth, $columnWidth);
 642      }
 643   
 644      /**
 645       * Called after rendering to cleanup cache data.
 646       */
 647      private function cleanup()
 648      {
 649          $this->effectiveColumnWidths = [];
 650          $this->numberOfColumns = null;
 651      }
 652   
 653      private static function initStyles()
 654      {
 655          $borderless = new TableStyle();
 656          $borderless
 657              ->setHorizontalBorderChar('=')
 658              ->setVerticalBorderChar(' ')
 659              ->setCrossingChar(' ')
 660          ;
 661   
 662          $compact = new TableStyle();
 663          $compact
 664              ->setHorizontalBorderChar('')
 665              ->setVerticalBorderChar(' ')
 666              ->setCrossingChar('')
 667              ->setCellRowContentFormat('%s')
 668          ;
 669   
 670          $styleGuide = new TableStyle();
 671          $styleGuide
 672              ->setHorizontalBorderChar('-')
 673              ->setVerticalBorderChar(' ')
 674              ->setCrossingChar(' ')
 675              ->setCellHeaderFormat('%s')
 676          ;
 677   
 678          return [
 679              'default' => new TableStyle(),
 680              'borderless' => $borderless,
 681              'compact' => $compact,
 682              'symfony-style-guide' => $styleGuide,
 683          ];
 684      }
 685   
 686      private function resolveStyle($name)
 687      {
 688          if ($name instanceof TableStyle) {
 689              return $name;
 690          }
 691   
 692          if (isset(self::$styles[$name])) {
 693              return self::$styles[$name];
 694          }
 695   
 696          throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
 697      }
 698  }
 699