1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Console\Helper; 13 14use Symfony\Component\Console\Exception\InvalidArgumentException; 15use Symfony\Component\Console\Exception\RuntimeException; 16use Symfony\Component\Console\Formatter\OutputFormatter; 17use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; 18use Symfony\Component\Console\Output\ConsoleSectionOutput; 19use Symfony\Component\Console\Output\OutputInterface; 20 21/** 22 * Provides helpers to display a table. 23 * 24 * @author Fabien Potencier <fabien@symfony.com> 25 * @author Саша Стаменковић <umpirsky@gmail.com> 26 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> 27 * @author Max Grigorian <maxakawizard@gmail.com> 28 * @author Dany Maillard <danymaillard93b@gmail.com> 29 */ 30class Table 31{ 32 private const SEPARATOR_TOP = 0; 33 private const SEPARATOR_TOP_BOTTOM = 1; 34 private const SEPARATOR_MID = 2; 35 private const SEPARATOR_BOTTOM = 3; 36 private const BORDER_OUTSIDE = 0; 37 private const BORDER_INSIDE = 1; 38 39 private $headerTitle; 40 private $footerTitle; 41 42 /** 43 * Table headers. 44 */ 45 private $headers = []; 46 47 /** 48 * Table rows. 49 */ 50 private $rows = []; 51 52 /** 53 * Column widths cache. 54 */ 55 private $effectiveColumnWidths = []; 56 57 /** 58 * Number of columns cache. 59 * 60 * @var int 61 */ 62 private $numberOfColumns; 63 64 /** 65 * @var OutputInterface 66 */ 67 private $output; 68 69 /** 70 * @var TableStyle 71 */ 72 private $style; 73 74 /** 75 * @var array 76 */ 77 private $columnStyles = []; 78 79 /** 80 * User set column widths. 81 * 82 * @var array 83 */ 84 private $columnWidths = []; 85 private $columnMaxWidths = []; 86 87 private static $styles; 88 89 private $rendered = false; 90 91 public function __construct(OutputInterface $output) 92 { 93 $this->output = $output; 94 95 if (!self::$styles) { 96 self::$styles = self::initStyles(); 97 } 98 99 $this->setStyle('default'); 100 } 101 102 /** 103 * Sets a style definition. 104 * 105 * @param string $name The style name 106 * @param TableStyle $style A TableStyle instance 107 */ 108 public static function setStyleDefinition($name, TableStyle $style) 109 { 110 if (!self::$styles) { 111 self::$styles = self::initStyles(); 112 } 113 114 self::$styles[$name] = $style; 115 } 116 117 /** 118 * Gets a style definition by name. 119 * 120 * @param string $name The style name 121 * 122 * @return TableStyle 123 */ 124 public static function getStyleDefinition($name) 125 { 126 if (!self::$styles) { 127 self::$styles = self::initStyles(); 128 } 129 130 if (isset(self::$styles[$name])) { 131 return self::$styles[$name]; 132 } 133 134 throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); 135 } 136 137 /** 138 * Sets table style. 139 * 140 * @param TableStyle|string $name The style name or a TableStyle instance 141 * 142 * @return $this 143 */ 144 public function setStyle($name) 145 { 146 $this->style = $this->resolveStyle($name); 147 148 return $this; 149 } 150 151 /** 152 * Gets the current table style. 153 * 154 * @return TableStyle 155 */ 156 public function getStyle() 157 { 158 return $this->style; 159 } 160 161 /** 162 * Sets table column style. 163 * 164 * @param int $columnIndex Column index 165 * @param TableStyle|string $name The style name or a TableStyle instance 166 * 167 * @return $this 168 */ 169 public function setColumnStyle($columnIndex, $name) 170 { 171 $columnIndex = (int) $columnIndex; 172 173 $this->columnStyles[$columnIndex] = $this->resolveStyle($name); 174 175 return $this; 176 } 177 178 /** 179 * Gets the current style for a column. 180 * 181 * If style was not set, it returns the global table style. 182 * 183 * @param int $columnIndex Column index 184 * 185 * @return TableStyle 186 */ 187 public function getColumnStyle($columnIndex) 188 { 189 return $this->columnStyles[$columnIndex] ?? $this->getStyle(); 190 } 191 192 /** 193 * Sets the minimum width of a column. 194 * 195 * @param int $columnIndex Column index 196 * @param int $width Minimum column width in characters 197 * 198 * @return $this 199 */ 200 public function setColumnWidth($columnIndex, $width) 201 { 202 $this->columnWidths[(int) $columnIndex] = (int) $width; 203 204 return $this; 205 } 206 207 /** 208 * Sets the minimum width of all columns. 209 * 210 * @param array $widths 211 * 212 * @return $this 213 */ 214 public function setColumnWidths(array $widths) 215 { 216 $this->columnWidths = []; 217 foreach ($widths as $index => $width) { 218 $this->setColumnWidth($index, $width); 219 } 220 221 return $this; 222 } 223 224 /** 225 * Sets the maximum width of a column. 226 * 227 * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while 228 * formatted strings are preserved. 229 * 230 * @return $this 231 */ 232 public function setColumnMaxWidth(int $columnIndex, int $width): self 233 { 234 if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { 235 throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter()))); 236 } 237 238 $this->columnMaxWidths[$columnIndex] = $width; 239 240 return $this; 241 } 242 243 public function setHeaders(array $headers) 244 { 245 $headers = array_values($headers); 246 if (!empty($headers) && !\is_array($headers[0])) { 247 $headers = [$headers]; 248 } 249 250 $this->headers = $headers; 251 252 return $this; 253 } 254 255 public function setRows(array $rows) 256 { 257 $this->rows = []; 258 259 return $this->addRows($rows); 260 } 261 262 public function addRows(array $rows) 263 { 264 foreach ($rows as $row) { 265 $this->addRow($row); 266 } 267 268 return $this; 269 } 270 271 public function addRow($row) 272 { 273 if ($row instanceof TableSeparator) { 274 $this->rows[] = $row; 275 276 return $this; 277 } 278 279 if (!\is_array($row)) { 280 throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); 281 } 282 283 $this->rows[] = array_values($row); 284 285 return $this; 286 } 287 288 /** 289 * Adds a row to the table, and re-renders the table. 290 */ 291 public function appendRow($row): self 292 { 293 if (!$this->output instanceof ConsoleSectionOutput) { 294 throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); 295 } 296 297 if ($this->rendered) { 298 $this->output->clear($this->calculateRowCount()); 299 } 300 301 $this->addRow($row); 302 $this->render(); 303 304 return $this; 305 } 306 307 public function setRow($column, array $row) 308 { 309 $this->rows[$column] = $row; 310 311 return $this; 312 } 313 314 public function setHeaderTitle(?string $title): self 315 { 316 $this->headerTitle = $title; 317 318 return $this; 319 } 320 321 public function setFooterTitle(?string $title): self 322 { 323 $this->footerTitle = $title; 324 325 return $this; 326 } 327 328 /** 329 * Renders table to output. 330 * 331 * Example: 332 * 333 * +---------------+-----------------------+------------------+ 334 * | ISBN | Title | Author | 335 * +---------------+-----------------------+------------------+ 336 * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 337 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 338 * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | 339 * +---------------+-----------------------+------------------+ 340 */ 341 public function render() 342 { 343 $rows = array_merge($this->headers, [$divider = new TableSeparator()], $this->rows); 344 $this->calculateNumberOfColumns($rows); 345 346 $rows = $this->buildTableRows($rows); 347 $this->calculateColumnsWidth($rows); 348 349 $isHeader = true; 350 $isFirstRow = false; 351 foreach ($rows as $row) { 352 if ($divider === $row) { 353 $isHeader = false; 354 $isFirstRow = true; 355 356 continue; 357 } 358 if ($row instanceof TableSeparator) { 359 $this->renderRowSeparator(); 360 361 continue; 362 } 363 if (!$row) { 364 continue; 365 } 366 367 if ($isHeader || $isFirstRow) { 368 if ($isFirstRow) { 369 $this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM); 370 $isFirstRow = false; 371 } else { 372 $this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat()); 373 } 374 } 375 376 $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); 377 } 378 $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); 379 380 $this->cleanup(); 381 $this->rendered = true; 382 } 383 384 /** 385 * Renders horizontal header separator. 386 * 387 * Example: 388 * 389 * +-----+-----------+-------+ 390 */ 391 private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null) 392 { 393 if (0 === $count = $this->numberOfColumns) { 394 return; 395 } 396 397 $borders = $this->style->getBorderChars(); 398 if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { 399 return; 400 } 401 402 $crossings = $this->style->getCrossingChars(); 403 if (self::SEPARATOR_MID === $type) { 404 list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; 405 } elseif (self::SEPARATOR_TOP === $type) { 406 list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; 407 } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { 408 list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; 409 } else { 410 list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; 411 } 412 413 $markup = $leftChar; 414 for ($column = 0; $column < $count; ++$column) { 415 $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); 416 $markup .= $column === $count - 1 ? $rightChar : $midChar; 417 } 418 419 if (null !== $title) { 420 $titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)); 421 $markupLength = Helper::strlen($markup); 422 if ($titleLength > $limit = $markupLength - 4) { 423 $titleLength = $limit; 424 $formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, '')); 425 $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); 426 } 427 428 $titleStart = ($markupLength - $titleLength) / 2; 429 if (false === mb_detect_encoding($markup, null, true)) { 430 $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); 431 } else { 432 $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); 433 } 434 } 435 436 $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); 437 } 438 439 /** 440 * Renders vertical column separator. 441 */ 442 private function renderColumnSeparator($type = self::BORDER_OUTSIDE) 443 { 444 $borders = $this->style->getBorderChars(); 445 446 return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); 447 } 448 449 /** 450 * Renders table row. 451 * 452 * Example: 453 * 454 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 455 */ 456 private function renderRow(array $row, string $cellFormat) 457 { 458 $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); 459 $columns = $this->getRowColumns($row); 460 $last = \count($columns) - 1; 461 foreach ($columns as $i => $column) { 462 $rowContent .= $this->renderCell($row, $column, $cellFormat); 463 $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); 464 } 465 $this->output->writeln($rowContent); 466 } 467 468 /** 469 * Renders table cell with padding. 470 */ 471 private function renderCell(array $row, int $column, string $cellFormat) 472 { 473 $cell = isset($row[$column]) ? $row[$column] : ''; 474 $width = $this->effectiveColumnWidths[$column]; 475 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 476 // add the width of the following columns(numbers of colspan). 477 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { 478 $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; 479 } 480 } 481 482 // str_pad won't work properly with multi-byte strings, we need to fix the padding 483 if (false !== $encoding = mb_detect_encoding($cell, null, true)) { 484 $width += \strlen($cell) - mb_strwidth($cell, $encoding); 485 } 486 487 $style = $this->getColumnStyle($column); 488 489 if ($cell instanceof TableSeparator) { 490 return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); 491 } 492 493 $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); 494 $content = sprintf($style->getCellRowContentFormat(), $cell); 495 496 return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); 497 } 498 499 /** 500 * Calculate number of columns for this table. 501 */ 502 private function calculateNumberOfColumns($rows) 503 { 504 $columns = [0]; 505 foreach ($rows as $row) { 506 if ($row instanceof TableSeparator) { 507 continue; 508 } 509 510 $columns[] = $this->getNumberOfColumns($row); 511 } 512 513 $this->numberOfColumns = max($columns); 514 } 515 516 private function buildTableRows($rows) 517 { 518 /** @var WrappableOutputFormatterInterface $formatter */ 519 $formatter = $this->output->getFormatter(); 520 $unmergedRows = []; 521 for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { 522 $rows = $this->fillNextRows($rows, $rowKey); 523 524 // Remove any new line breaks and replace it with a new line 525 foreach ($rows[$rowKey] as $column => $cell) { 526 $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; 527 528 if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) { 529 $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); 530 } 531 if (!strstr($cell, "\n")) { 532 continue; 533 } 534 $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); 535 $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; 536 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); 537 foreach ($lines as $lineKey => $line) { 538 if ($colspan > 1) { 539 $line = new TableCell($line, ['colspan' => $colspan]); 540 } 541 if (0 === $lineKey) { 542 $rows[$rowKey][$column] = $line; 543 } else { 544 $unmergedRows[$rowKey][$lineKey][$column] = $line; 545 } 546 } 547 } 548 } 549 550 return new TableRows(function () use ($rows, $unmergedRows) { 551 foreach ($rows as $rowKey => $row) { 552 yield $this->fillCells($row); 553 554 if (isset($unmergedRows[$rowKey])) { 555 foreach ($unmergedRows[$rowKey] as $row) { 556 yield $row; 557 } 558 } 559 } 560 }); 561 } 562 563 private function calculateRowCount(): int 564 { 565 $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); 566 567 if ($this->headers) { 568 ++$numberOfRows; // Add row for header separator 569 } 570 571 ++$numberOfRows; // Add row for footer separator 572 573 return $numberOfRows; 574 } 575 576 /** 577 * fill rows that contains rowspan > 1. 578 * 579 * @throws InvalidArgumentException 580 */ 581 private function fillNextRows(array $rows, int $line): array 582 { 583 $unmergedRows = []; 584 foreach ($rows[$line] as $column => $cell) { 585 if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { 586 throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing __toString, %s given.', \gettype($cell))); 587 } 588 if ($cell instanceof TableCell && $cell->getRowspan() > 1) { 589 $nbLines = $cell->getRowspan() - 1; 590 $lines = [$cell]; 591 if (strstr($cell, "\n")) { 592 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); 593 $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; 594 595 $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); 596 unset($lines[0]); 597 } 598 599 // create a two dimensional array (rowspan x colspan) 600 $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); 601 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { 602 $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; 603 $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]); 604 if ($nbLines === $unmergedRowKey - $line) { 605 break; 606 } 607 } 608 } 609 } 610 611 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { 612 // we need to know if $unmergedRow will be merged or inserted into $rows 613 if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { 614 foreach ($unmergedRow as $cellKey => $cell) { 615 // insert cell into row at cellKey position 616 array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); 617 } 618 } else { 619 $row = $this->copyRow($rows, $unmergedRowKey - 1); 620 foreach ($unmergedRow as $column => $cell) { 621 if (!empty($cell)) { 622 $row[$column] = $unmergedRow[$column]; 623 } 624 } 625 array_splice($rows, $unmergedRowKey, 0, [$row]); 626 } 627 } 628 629 return $rows; 630 } 631 632 /** 633 * fill cells for a row that contains colspan > 1. 634 */ 635 private function fillCells($row) 636 { 637 $newRow = []; 638 foreach ($row as $column => $cell) { 639 $newRow[] = $cell; 640 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 641 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { 642 // insert empty value at column position 643 $newRow[] = ''; 644 } 645 } 646 } 647 648 return $newRow ?: $row; 649 } 650 651 private function copyRow(array $rows, int $line): array 652 { 653 $row = $rows[$line]; 654 foreach ($row as $cellKey => $cellValue) { 655 $row[$cellKey] = ''; 656 if ($cellValue instanceof TableCell) { 657 $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); 658 } 659 } 660 661 return $row; 662 } 663 664 /** 665 * Gets number of columns by row. 666 */ 667 private function getNumberOfColumns(array $row): int 668 { 669 $columns = \count($row); 670 foreach ($row as $column) { 671 $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; 672 } 673 674 return $columns; 675 } 676 677 /** 678 * Gets list of columns for the given row. 679 */ 680 private function getRowColumns(array $row): array 681 { 682 $columns = range(0, $this->numberOfColumns - 1); 683 foreach ($row as $cellKey => $cell) { 684 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 685 // exclude grouped columns. 686 $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); 687 } 688 } 689 690 return $columns; 691 } 692 693 /** 694 * Calculates columns widths. 695 */ 696 private function calculateColumnsWidth(iterable $rows) 697 { 698 for ($column = 0; $column < $this->numberOfColumns; ++$column) { 699 $lengths = []; 700 foreach ($rows as $row) { 701 if ($row instanceof TableSeparator) { 702 continue; 703 } 704 705 foreach ($row as $i => $cell) { 706 if ($cell instanceof TableCell) { 707 $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); 708 $textLength = Helper::strlen($textContent); 709 if ($textLength > 0) { 710 $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); 711 foreach ($contentColumns as $position => $content) { 712 $row[$i + $position] = $content; 713 } 714 } 715 } 716 } 717 718 $lengths[] = $this->getCellWidth($row, $column); 719 } 720 721 $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2; 722 } 723 } 724 725 private function getColumnSeparatorWidth(): int 726 { 727 return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); 728 } 729 730 private function getCellWidth(array $row, int $column): int 731 { 732 $cellWidth = 0; 733 734 if (isset($row[$column])) { 735 $cell = $row[$column]; 736 $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); 737 } 738 739 $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0; 740 $cellWidth = max($cellWidth, $columnWidth); 741 742 return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; 743 } 744 745 /** 746 * Called after rendering to cleanup cache data. 747 */ 748 private function cleanup() 749 { 750 $this->effectiveColumnWidths = []; 751 $this->numberOfColumns = null; 752 } 753 754 private static function initStyles() 755 { 756 $borderless = new TableStyle(); 757 $borderless 758 ->setHorizontalBorderChars('=') 759 ->setVerticalBorderChars(' ') 760 ->setDefaultCrossingChar(' ') 761 ; 762 763 $compact = new TableStyle(); 764 $compact 765 ->setHorizontalBorderChars('') 766 ->setVerticalBorderChars(' ') 767 ->setDefaultCrossingChar('') 768 ->setCellRowContentFormat('%s') 769 ; 770 771 $styleGuide = new TableStyle(); 772 $styleGuide 773 ->setHorizontalBorderChars('-') 774 ->setVerticalBorderChars(' ') 775 ->setDefaultCrossingChar(' ') 776 ->setCellHeaderFormat('%s') 777 ; 778 779 $box = (new TableStyle()) 780 ->setHorizontalBorderChars('─') 781 ->setVerticalBorderChars('│') 782 ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') 783 ; 784 785 $boxDouble = (new TableStyle()) 786 ->setHorizontalBorderChars('═', '─') 787 ->setVerticalBorderChars('║', '│') 788 ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') 789 ; 790 791 return [ 792 'default' => new TableStyle(), 793 'borderless' => $borderless, 794 'compact' => $compact, 795 'symfony-style-guide' => $styleGuide, 796 'box' => $box, 797 'box-double' => $boxDouble, 798 ]; 799 } 800 801 private function resolveStyle($name) 802 { 803 if ($name instanceof TableStyle) { 804 return $name; 805 } 806 807 if (isset(self::$styles[$name])) { 808 return self::$styles[$name]; 809 } 810 811 throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); 812 } 813} 814