Table.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <?php
  2. namespace fphammerle\helpers\table;
  3. class Table
  4. {
  5. use \fphammerle\helpers\PropertyAccessTrait;
  6. private $_rows = [];
  7. /**
  8. * @param array<array<mixed>> $cell_values
  9. */
  10. public function __construct($cell_values = [])
  11. {
  12. foreach($cell_values as $row_index => $row_values) {
  13. $this->setRow($row_index, new Row($row_values));
  14. }
  15. }
  16. /**
  17. * @throws InvalidArgumentException
  18. * @param integer $row_index
  19. * @return Row
  20. */
  21. public function getRow($row_index)
  22. {
  23. if(!is_int($row_index) || $row_index < 0) {
  24. throw new \InvalidArgumentException(
  25. sprintf('row index must be an integer >= 0, %s given', print_r($row_index, true))
  26. );
  27. }
  28. if(!isset($this->_rows[$row_index])) {
  29. $this->_rows[$row_index] = new Row;
  30. }
  31. return $this->_rows[$row_index];
  32. }
  33. /**
  34. * @param Row $row
  35. */
  36. public function appendRow(Row $row)
  37. {
  38. $this->_rows[] = $row;
  39. }
  40. /**
  41. * @throws InvalidArgumentException
  42. * @param integer $row_index
  43. * @param Row $row
  44. */
  45. public function setRow($row_index, Row $row)
  46. {
  47. if(!is_int($row_index) || $row_index < 0) {
  48. throw new \InvalidArgumentException(
  49. sprintf('row index must be an integer >= 0, %s given', print_r($row_index, true))
  50. );
  51. }
  52. $this->_rows[$row_index] = $row;
  53. }
  54. /**
  55. * @param integer $row_index
  56. * @param integer $column_index
  57. * @return Cell
  58. */
  59. public function getCell($row_index, $column_index)
  60. {
  61. return $this->getRow($row_index)->getCell($column_index);
  62. }
  63. /**
  64. * @param integer $column_index
  65. * @param mixed $value
  66. */
  67. public function setCellValue($row_index, $column_index, $value)
  68. {
  69. $this->getCell($row_index, $column_index)->value = $value;
  70. }
  71. /**
  72. * @return integer
  73. */
  74. public function getColumnsCount()
  75. {
  76. return sizeof($this->_rows) > 0
  77. ? max(array_map(function($r) { return $r->columnsCount; }, $this->_rows))
  78. : 0;
  79. }
  80. /**
  81. * @return integer
  82. */
  83. public function getRowsCount()
  84. {
  85. return sizeof($this->_rows) > 0
  86. ? max(array_keys($this->_rows)) + 1
  87. : 0;
  88. }
  89. /**
  90. * @return string
  91. */
  92. public function toCSV($delimiter = ',')
  93. {
  94. $columns_number = $this->columnsCount;
  95. $empty_row_csv = (new Row)->toCSV($delimiter, $columns_number);
  96. $rows_csv = [];
  97. $rows_number = $this->rowsCount;
  98. for($row_index = 0; $row_index < $rows_number; $row_index++) {
  99. $rows_csv[] = isset($this->_rows[$row_index])
  100. ? $this->_rows[$row_index]->toCSV($delimiter, $columns_number)
  101. : $empty_row_csv;
  102. }
  103. return implode('', $rows_csv);
  104. }
  105. /**
  106. * @return string
  107. */
  108. public function toText()
  109. {
  110. $rows_number = $this->rowsCount;
  111. $cols_number = $this->columnsCount;
  112. if($rows_number == 0) {
  113. return '';
  114. } elseif($cols_number == 0) {
  115. return str_repeat("\n", $rows_number);
  116. }
  117. $cols_max_length = [];
  118. $string_table = [];
  119. for($col_index = 0; $col_index < $cols_number; $col_index++) {
  120. $cols_max_length[$col_index] = 0;
  121. for($row_index = 0; $row_index < $rows_number; $row_index++) {
  122. $cell_value = $this->getCell($row_index, $col_index)->value;
  123. if($cell_value === false) {
  124. $cell_value_lines = ['0'];
  125. } else {
  126. $cell_value_lines = explode("\n", (string)$cell_value);
  127. assert(sizeof($cell_value_lines) > 0);
  128. }
  129. $string_table[$row_index][$col_index] = $cell_value_lines;
  130. $cols_max_length[$col_index] = max(
  131. max(array_map(
  132. function($line) { return strlen($line); },
  133. $cell_value_lines
  134. )),
  135. $cols_max_length[$col_index]
  136. );
  137. }
  138. }
  139. return implode("\n", array_map(
  140. function($row) use ($cols_number, $cols_max_length) {
  141. $lines_number = max(array_map(function($c) { return sizeof($c); }, $row));
  142. $lines = [];
  143. for($line_index = 0; $line_index < $lines_number; $line_index++) {
  144. $line = '';
  145. for($col_index = 0; $col_index < $cols_number; $col_index++) {
  146. $line .= str_pad(
  147. isset($row[$col_index][$line_index])
  148. ? $row[$col_index][$line_index]
  149. : '',
  150. $cols_max_length[$col_index]
  151. + ($col_index + 1 == $cols_number ? 0 : 1),
  152. ' ',
  153. STR_PAD_RIGHT
  154. );
  155. }
  156. $lines[] = $line;
  157. }
  158. return implode("\n", $lines);
  159. },
  160. $string_table
  161. )) . "\n";
  162. }
  163. /**
  164. * @param array<mixed> $rows associative array
  165. * @return Table
  166. */
  167. public static function fromAssociativeArray(array $rows)
  168. {
  169. $keys = [];
  170. foreach($rows as $row) {
  171. $keys = array_unique(array_merge($keys, array_keys($row)));
  172. }
  173. // array_unique preserves keys
  174. $keys = array_values($keys);
  175. $t = new self([$keys]);
  176. foreach($rows as $row) {
  177. $t->appendRow(new Row(array_map(
  178. function($key) use ($row) {
  179. return array_key_exists($key, $row) ? $row[$key] : null;
  180. },
  181. $keys
  182. )));
  183. }
  184. return $t;
  185. }
  186. }