diff --git a/examples/calculations.php b/examples/calculations.php index e97e580..29bf34d 100644 --- a/examples/calculations.php +++ b/examples/calculations.php @@ -4,7 +4,10 @@ use divengine\matrix; -// Create a matrix +$F_AMOUNT = fn($r, $c, matrix $m) => $m->{$r + .1} * $m->{$r + .2}; +$F_TOTAL = fn ($r, $c, matrix $m) => array_sum($m->vertical($c, 1, $r - 1)); +$F_CUMUL = fn ($r, $c, matrix $m) => $r == 1 ? $m->get($r, $c - 1) + : $m->get($r - 1, $c) + $m->get($r, $c - 1); $table = new matrix([ ["Product", "Price", "Count"], ["Apple", 10, 2], @@ -12,46 +15,39 @@ ["Orange", 6, 10], ]); -// Show the matrix -echo $table->format('txt', true); +echo $table . "\n"; $table->addColumn(); -$table->set(0, 3, "Amount"); +$table->{0.3} = "Amount"; // Fill the column with the product of the previous two columns -$table->fillVertical(3, 1, 3, - fn ($r, $c, matrix $m) - => $m->get($r, $c - 1) * $m->get($r, $c - 2)); +$table->fillVertical(3, 1, 3, $F_AMOUNT); // Add a row with the sum of the previous rows -$table->addRow(["Total", "", "", - fn ($r, $c, matrix $m) - => array_sum($m->vertical($c, 1, $r - 1))]); -echo $table->format('txt', true); +$table->addRow(["Total", "", "", $F_TOTAL]); +echo $table . "\n"; // Add a column with the sum of the previous columns $table->addColumn(); -$table->set(0, 4, "Cumul"); +$table->{0.4} = "Cumul"; // Fill the column with the sum of the previous column -$table->fillVertical(4, 1, 3, - fn ($r, $c, matrix $m) - => $r == 1 ? $m->get($r, $c - 1) - : $m->get($r - 1, $c) + $m->get($r, $c - 1)); - -echo $table->format('txt', true); +$table->fillVertical(4, 1, 3, $F_CUMUL); +echo $table . "\n"; // Change a value of the second row -$table->set(1, 1, 20); -echo $table->format('txt', true); +$table->{1.1} = 20; +echo $table . "\n"; // Remove the second row $table->removeRow(1); -echo $table->format('txt', true); +echo $table . "\n"; // Show ranges print_r($table->vertical(1, 1, 2)); print_r($table->horizontal(1, 1, 2)); +print_r($table->range(1, 1, 2, 2)); +echo "\n"; // Serialize echo $table->format('serialize'); \ No newline at end of file diff --git a/examples/cells.php b/examples/cells.php new file mode 100644 index 0000000..6c83387 --- /dev/null +++ b/examples/cells.php @@ -0,0 +1,42 @@ +get(1, 1); +echo $m->{1.1}; +echo "\n"; + +// echo $m->{0.2}; +echo $m->{.2}; +echo "\n"; + +// echo $m->{2.2}; +echo $m->{-1.2}; +echo "\n"; + +for ($i = 0; $i < 3; $i++) + for ($j = 0; $j < 3; $j++) + echo $m->{$i + ($j / 10)} . " "; +echo "\n"; + +for ($i = 0; $i < 3; $i++) + for ($j = 0; $j < 3; $j++) + echo $m->{($i + ($j / 10) + 1) * -1} . " "; +echo "\n"; + +for ($i = 0; $i < 3; $i++) + for ($j = 0; $j < 3; $j++) + echo $m->{"$i.$j"} . " "; + +echo "\n"; + +$m->{0.0} = 10; +echo $m; \ No newline at end of file diff --git a/examples/create.php b/examples/create.php new file mode 100644 index 0000000..aa6102c --- /dev/null +++ b/examples/create.php @@ -0,0 +1,28 @@ +formatTXT(false); +echo PHP_EOL; + +// Create a new matrix using the static method create +$m2 = matrix::create($array); +echo $m2->formatTXT(false); +echo PHP_EOL; + +// Create an matrix from dimensions +$m3 = matrix::dimension(5, 5, 0); +echo $m3->formatTXT(false); +echo PHP_EOL; + +// Create an matrix from a string +$m4 = matrix::fromJSONFile(__DIR__.'/data/matrix.json'); +echo $m4->formatTXT(false); +echo PHP_EOL; diff --git a/examples/custom.php b/examples/custom.php new file mode 100644 index 0000000..4a14f15 --- /dev/null +++ b/examples/custom.php @@ -0,0 +1,34 @@ + $m->{$r + .1} * $m->{$r +.2}; +$F_TOTAL = fn($r, $c, matrix $m) => array_sum($m->vertical($c, 1, $m->rows - 1)); +$F_AVG = fn($r, $c, matrix $m) => $F_TOTAL($r, $c, $m) / ($m->rows - 1); + +class ProductsTable extends matrix +{ + public function __construct(array $products) + { + global $F_AMOUNT, $F_AVG, $F_TOTAL; + + $data = [["Name", "Price", "Qty", "Total"]]; + foreach ($products as $product) + { + $data[] = [$product->name, $product->price, $product->quantity, $F_AMOUNT]; + } + + $data[] = ["Total", $F_AVG, $F_TOTAL, $F_TOTAL]; + + parent::__construct($data); + } +} + +echo new ProductsTable([ + (object) ["name" => "Apple", "price" => 1.5, "quantity" => 10], + (object) ["name" => "Banana", "price" => 2.5, "quantity" => 5], + (object) ["name" => "Orange", "price" => 3.5, "quantity" => 3], + (object) ["name" => "Kiwi", "price" => 4.5, "quantity" => 1], +]); diff --git a/examples/data/matrix.json b/examples/data/matrix.json new file mode 100644 index 0000000..106a76f --- /dev/null +++ b/examples/data/matrix.json @@ -0,0 +1,5 @@ +[ + [1,2,3], + [4,5,6], + [7,8,9] +] \ No newline at end of file diff --git a/examples/edit.php b/examples/edit.php new file mode 100644 index 0000000..b3f2f80 --- /dev/null +++ b/examples/edit.php @@ -0,0 +1,75 @@ +insertAfterRow(1, [10, 11, 12]); + +echo $m; +echo "\n"; + +$m->insertBeforeRow(1, [13, 14, 15]); + +echo $m; +echo "\n"; + +$m->insertAfterColumn(1, 0); +echo $m; +echo "\n"; + +$m->insertBeforeColumn(1, 0); +echo $m; +echo "\n"; + +$m->removeColumn(1); +echo $m; +echo "\n"; + +$m->removeRow(2); +echo $m; +echo "\n"; + +$m->removeColumn(2); +echo $m; +echo "\n"; + +$m->addColumn(5); +echo $m; +echo "\n"; + +$m->removeColumnRange(1, 3); +echo $m; +echo "\n"; + +$m->addColumn(6); +$m->addRow([2, 2]); +echo $m; +echo "\n"; + +$m->removeRowRange(1, 3); +echo $m; +echo "\n"; + +$m->addColumn(7); +$m->addRow([3, 3, 3]); +echo $m; +echo "\n"; + +$m->fillRange(1, 1, 2, 2, 0); +echo $m; +echo "\n"; + +$m->insertAfterColumn(1, [1, 2, 3]); +echo $m; +echo "\n"; + +$m->insertBeforeColumn(1, [4, 5, 6]); +echo $m; +echo "\n"; diff --git a/examples/extends.php b/examples/extends.php new file mode 100644 index 0000000..cc00563 --- /dev/null +++ b/examples/extends.php @@ -0,0 +1,49 @@ + $m->get($r, 0) * $m->get($r, 2), "x"); + } +} + +class AdditionTable extends MathTable +{ + public function __construct($number = 1, $size = 10) + { + parent::__construct($number, $size, fn ($r, $c, $m) => $m->get($r, 0) + $m->get($r, 2), "+"); + } +} + +for ($i = 1; $i <= 10; $i++) +{ + echo new MultiplicationTable($i); + echo PHP_EOL; +} + +echo PHP_EOL; + +for ($i = 1; $i <= 10; $i++) +{ + echo new AdditionTable($i); + echo PHP_EOL; +} \ No newline at end of file diff --git a/examples/grouping.php b/examples/grouping.php index 0076315..be4f4e7 100644 --- a/examples/grouping.php +++ b/examples/grouping.php @@ -19,7 +19,7 @@ ]); // Show the matrix -echo $table->formatTXT(true); +echo $table; // Group by $result = $table->groupBy([0], function($key, $group){ @@ -33,5 +33,5 @@ echo "\n"; $groupBy = new matrix(array_values($result)); $groupBy->addRow(["Product", "Total"], onTop: true); -echo $groupBy->formatTXT(true); +echo $groupBy; diff --git a/examples/handlers/404.php b/examples/handlers/404.php new file mode 100644 index 0000000..0e95d89 --- /dev/null +++ b/examples/handlers/404.php @@ -0,0 +1,8 @@ +console.log('{$moment} - {$url}');"; + return true; +}; \ No newline at end of file diff --git a/examples/handlers/login.php b/examples/handlers/login.php new file mode 100644 index 0000000..e2d98cf --- /dev/null +++ b/examples/handlers/login.php @@ -0,0 +1,7 @@ + isLogged()] +]); + +$F_MATCH = fn ($r, $c, $m) => matchRoute($m->{$r}); +$F_PASS = fn ($r, $c, $m) => ($context->{1.1} || $m->{$r + .2}) && $m->{$r + .3}; +$F_HANDLER = fn ($r, $c, $m) => $m->{$r + .4} ? $m->{$r + .1} : 'login'; +$F_HANDLER_PATH = fn ($r, $c, $m) => __DIR__ . "/handlers/{$m->{$r + .5}}.php"; +$F_LISTENER = fn ($r, $c, matrix $m) => $m->{$r + .3} ? (require($m->{$r + .6}))() : null; +$F_NOTHING_MATCH = fn ($r, $c, matrix $m) => array_reduce($m->vertical($c, 1, $r - 3), fn ($c, $i) => !$c && !$i, false); + +$routes = new matrix([ + ["Route", "Controller", "Public", "Match", "Pass", "Handler", "Handler Path", "Listener"], + // ----------------------------------------------------------------------------------------------------- + ["/", "home", true, $F_MATCH, $F_PASS, $F_HANDLER, $F_HANDLER_PATH, $F_LISTENER], + ["/about", "about", true, $F_MATCH, $F_PASS, $F_HANDLER, $F_HANDLER_PATH, $F_LISTENER], + ["/login", "login", true, $F_MATCH, $F_PASS, $F_HANDLER, $F_HANDLER_PATH, $F_LISTENER], + ["/admin", "admin", false, $F_MATCH, $F_PASS, $F_HANDLER, $F_HANDLER_PATH, $F_LISTENER], + // ----------------------------------------------------------------------------------------------------- + ["*", "log", true, $F_MATCH, $F_PASS, $F_HANDLER, $F_HANDLER_PATH, $F_LISTENER], + ["*", "404", true, $F_NOTHING_MATCH, $F_PASS, $F_HANDLER, $F_HANDLER_PATH, $F_LISTENER] +]); + +echo ""; diff --git a/src/matrix.php b/src/matrix.php index c98a392..840bd61 100644 --- a/src/matrix.php +++ b/src/matrix.php @@ -24,22 +24,30 @@ * * @package divengine/matrix * @author Rafa Rodriguez @rafageist [https://rafageist.com] - * @version 1.1.0 + * @version 1.2.0 * * @link https://divengine.org/docs/div-php-matrix * @link https://github.com/divengine/matrix */ +use Closure; use SimpleXMLElement; use InvalidArgumentException; class matrix { - public static $version = '1.1.0'; - private $matrix; - private $matrix_original; - private $evaluatedCells = []; - private static $disableEvallAll = false; + public static string $version = '1.2.0'; + + /** @var array> $matrix */ + private array $matrix = []; + + /** @var array> $matrix_original */ + private array $matrix_original = []; + + /** @var array> $evaluatedCells */ + private array $evaluatedCells = []; + + private static bool $disableEvallAll = false; public const FORMAT_CSV = "CSV"; public const FORMAT_XML = "XML"; public const FORMAT_JSON = "JSON"; @@ -50,37 +58,189 @@ class matrix public const FORMAT_YAML = "YAML"; public const FORMAT_TXT = "TXT"; public const FORMAT_SQL = "SQL"; - private $defaultFormat = self::FORMAT_TXT; - public static $workbook = []; + private string $defaultFormat = self::FORMAT_TXT; + + /** @var array $workbook */ + public static array $workbook = []; /** * Constructor * - * @param array $matrix + * @param array|object> $matrixData * * @throws InvalidArgumentException */ - public function __construct(array $matrix) + public function __construct(array $matrixData) { // convert object rows to array - foreach ($matrix as $rowIndex => $row) { + /** @var array> $data */ + $data = []; + foreach ($matrixData as $rowIndex => $row) { if (is_object($row)) { - $matrix[$rowIndex] = (array) $row; + $data[$rowIndex] = (array) $row; + } else { + $data[$rowIndex] = $row; } } - $this->validateMatrix($matrix); - $this->matrix = $matrix; - $this->matrix_original = $matrix; + $this->validateMatrix($data); + $this->matrix = $data; + $this->matrix_original = $data; self::$workbook[] = $this; self::evaluateAll(); } + /** + * Create a matrix + * + * @param array|object> $matrix + * + * @return matrix + */ + public static function create(array $matrix) + { + return new self($matrix); + } + + /** + * Create a array from dimensions + * + * @param int $rows + * @param int $cols + * @param mixed $value + * + * @return array> + */ + public static function createArrayFromDims(int $rows, int $cols, $value = null) + { + $matrix = []; + for ($i = 0; $i < $rows; $i++) { + $matrix[] = array_fill(0, $cols, $value); + } + return $matrix; + } + + /** + * Create a matrix from dimensions + * + * @param int $rows + * @param int $cols + * @param mixed $value + * + * @return matrix + */ + public static function dimension(int $rows, int $cols, $value = null) + { + return new self(self::createArrayFromDims($rows, $cols, $value)); + } + + /** + * Create a matrix from a CSV file + * + * @param string $filename + * @param string $delimiter + * @param string $enclosure + * @param string $escape + * + * @return matrix + */ + public static function fromCSVFile(string $filename, string $delimiter = ',', string $enclosure = '"', string $escape = '\\'): matrix + { + $matrix = []; + if (($handle = fopen($filename, "r")) !== false) { + while (($data = fgetcsv($handle, 0, $delimiter, $enclosure, $escape)) !== false) { + $matrix[] = $data; + } + fclose($handle); + } + return new self($matrix); + } + + /** + * Create a matrix from a JSON file + * + * @param string $filename + * + * @throws InvalidArgumentException + * + * @return matrix + */ + public static function fromJSONFile(string $filename): matrix + { + $json = file_get_contents($filename); + if ($json) { + $matrix = json_decode($json, true); + + /** @var array|object> $matrix */ + return new self($matrix); + } + + throw new InvalidArgumentException('Invalid JSON file'); + } + + /** + * Create a matrix from a TXT file + * + * @param string $filename + * @param string $delimiter + * + * @return matrix + */ + public static function fromTXTFile(string $filename, string $delimiter = "\t"): matrix + { + $matrix = []; + if (($handle = fopen($filename, "r")) !== false) { + while (($data = fgetcsv($handle, 0, $delimiter)) !== false) { + $matrix[] = $data; + } + fclose($handle); + } + return new self($matrix); + } + + /** + * Create a matrix from a serialized file + * + * @param string $filename + * + * @throws InvalidArgumentException + * + * @return matrix + */ + public static function fromSerializedFile(string $filename): matrix + { + $serialized = file_get_contents($filename); + if ($serialized) { + $matrix = unserialize($serialized); + + /** @var array|object> $matrix */ + return new self($matrix); + } + + throw new InvalidArgumentException('Invalid serialized file'); + } + + /** + * Create a matrix from a serialized string + * + * @param string $string + * + * @return matrix + */ + + public static function fromSerializedString(string $string): matrix + { + $matrix = unserialize($string); + + /** @var array|object> $matrix */ + return new self($matrix); + } + /** * Validates the matrix * - * @param array $matrix + * @param array> $matrix * * @throws InvalidArgumentException * @@ -99,9 +259,12 @@ public function validateMatrix(array $matrix): void /** * Add a row * - * @param array $row + * @param array $row + * @param bool $onTop * - * @return int + * @throws InvalidArgumentException + * + * @return void */ public function addRow(array $row, bool $onTop = false): void { @@ -167,7 +330,7 @@ public function removeRow(int $index): void /** * Get data * - * @return array + * @return array> */ public function getMatrix(): array { @@ -177,7 +340,7 @@ public function getMatrix(): array /** * Validate a row * - * @param array $row + * @param array $row * * @throws InvalidArgumentException * @@ -249,33 +412,33 @@ public function formatCSV(): string * * @return string */ - public function formatXML(string $rootTag = 'root', bool $firstRowAsHeaders = false) + public function formatXML(string $rootTag = 'root', bool $firstRowAsHeaders = false): string { $xml = new SimpleXMLElement("<{$rootTag}/>"); if ($firstRowAsHeaders) { $fields = $xml->addChild('fields'); foreach ($this->matrix[0] as $field) { - $fields->addChild('field', $field); + $fields->addChild('field', $field.""); } $data = $xml->addChild('data'); foreach (array_slice($this->matrix, 1) as $row) { $item = $data->addChild('row'); foreach ($this->matrix[0] as $index => $field) { - $item->addChild($field, $row[$index]); + $item->addChild($field."", $row[$index].""); } } } else { foreach ($this->matrix as $row) { $item = $xml->addChild('row'); foreach ($row as $index => $field) { - $item->addChild('field', $field); + $item->addChild('field', $field.""); } } } - return $xml->asXML(); + return $xml->asXML() . ""; } /** @@ -285,7 +448,7 @@ public function formatXML(string $rootTag = 'root', bool $firstRowAsHeaders = fa * @param bool $asObjects * @return string */ - public function formatJSON(bool $asObjects = false) + public function formatJSON(bool $asObjects = false): string { if ($asObjects) { $result = []; @@ -293,15 +456,15 @@ public function formatJSON(bool $asObjects = false) // convert header names to lower snake case $header = array_map(function ($field) { - return str_replace(' ', '_', strtolower($field)); + return str_replace(' ', '_', strtolower($field."")); }, $this->matrix[0]); // combine header names with row values $result[] = array_combine($header, $row); } - return json_encode($result); + return json_encode($result) . ""; } - return json_encode($this->matrix); + return json_encode($this->matrix) . ""; } /** @@ -310,9 +473,9 @@ public function formatJSON(bool $asObjects = false) * * @return string */ - public function formatSerialize() + public function formatSerialize(): string { - return serialize($this->matrix); + return serialize($this->matrix) . ""; } /** @@ -395,13 +558,22 @@ public function formatYAML(bool $firstRowAsHeaders = false): string $array = $this->matrix; $yaml = ''; + $headers = []; + + // by default headers are a list of consecutive numbers + for ($i = 0; $i < count($array[0]); $i++) { + $headers[] = $i; + } + if ($firstRowAsHeaders) { // Process the first row (column names) $headers = array_shift($array); $yaml .= 'fields:' . PHP_EOL; - foreach ($headers as $header) { - $formattedHeader = str_replace(' ', '_', strtolower($header)); - $yaml .= " - $formattedHeader" . PHP_EOL; + if (is_array($headers)) { + foreach ($headers as $header) { + $formattedHeader = str_replace(' ', '_', strtolower($header."")); + $yaml .= " - $formattedHeader" . PHP_EOL; + } } } @@ -411,9 +583,12 @@ public function formatYAML(bool $firstRowAsHeaders = false): string $yaml .= ' - '; $firstKey = true; + $i = 0; foreach ($item as $key => $value) { + $i++; // Convert the key to lowercase and replace spaces with underscores - $formattedKey = str_replace(' ', '_', strtolower($headers[$key])); + $unformattedKey = $headers[$key] ?? "$i"; + $formattedKey = str_replace(' ', '_', strtolower($unformattedKey."")); // Add the first field on the same line with the dash, the rest aligned if (!$firstKey) { @@ -421,7 +596,7 @@ public function formatYAML(bool $firstRowAsHeaders = false): string } // Key and value for each pair - $yaml .= "$formattedKey: $value" . PHP_EOL; + $yaml .= $formattedKey.": ".$value . PHP_EOL; $firstKey = false; } @@ -470,7 +645,7 @@ public function formatSQL(string $tableName, bool $firstRowAsHeaders = false): s // headers as snake case $headers = array_map(function ($field) { - return str_replace(' ', '_', strtolower($field)); + return str_replace(' ', '_', strtolower($field."")); }, $this->matrix[0]); $sql = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $headers) . ') VALUES' . PHP_EOL; @@ -489,7 +664,7 @@ public function formatSQL(string $tableName, bool $firstRowAsHeaders = false): s } elseif (is_object($value)) { $row[$index] = json_encode($value); } else { - $row[$index] = "$value"; + $row[$index] = $value.""; } } @@ -534,12 +709,14 @@ public function fillHorizontal(int $row, int $from, int $to, mixed $value): void * * @param int $column * @param int $from + * @param int $to + * @param mixed $value * * @throws InvalidArgumentException * * @return void */ - public function fillVertical(int $column, int $from, int $to, $value): void + public function fillVertical(int $column, int $from, int $to, mixed $value): void { if ($from < 0 || $to > count($this->matrix)) { throw new InvalidArgumentException('Invalid range'); @@ -554,6 +731,48 @@ public function fillVertical(int $column, int $from, int $to, $value): void self::evaluateAll(); } + /** + * Fill a range + * + * @param int $rowFrom + * @param int $columnFrom + * @param int $rowTo + * @param int $columnTo + * + * @throws InvalidArgumentException + * + * @return void + */ + public function fillRange(int $rowFrom, int $columnFrom, int $rowTo, int $columnTo, mixed $value): void + { + $totalRows = count($this->matrix); + + if ($totalRows == 0) { + return; + } + + $totalColumns = count($this->matrix[0]); + + ( + $rowFrom >= 0 + and $rowTo < $totalRows + and $columnFrom >= 0 + and $columnTo < $totalColumns + and $rowFrom <= $rowTo + and $columnFrom <= $columnTo + ) or throw new InvalidArgumentException('Invalid range'); + + self::$disableEvallAll = true; + for ($i = $rowFrom; $i <= $rowTo; $i++) { + for ($j = $columnFrom; $j <= $columnTo; $j++) { + $this->set($i, $j, $value); + } + } + self::$disableEvallAll = false; + + self::evaluateAll(); + } + /** * Evaluate a cell * @@ -603,12 +822,13 @@ public function validateCoordinates(int $row, int $column): void * * @param int $row * @param int $column + * @param mixed $value * * @throws InvalidArgumentException * * @return void */ - public function set(int $row, int $column, $value): void + public function set(int $row, int $column, mixed $value): void { $this->validateCoordinates($row, $column); @@ -689,7 +909,7 @@ public static function evaluateAll(): void * * @throws InvalidArgumentException * - * @return array + * @return array */ public function horizontal(int $row, int $from, int $to): array { @@ -709,7 +929,7 @@ public function horizontal(int $row, int $from, int $to): array * * @throws InvalidArgumentException * - * @return array + * @return array */ public function vertical(int $column, int $from, int $to): array { @@ -734,19 +954,19 @@ public function vertical(int $column, int $from, int $to): array * * @throws InvalidArgumentException * - * @return array + * @return array> */ public function range(int $rowFrom, int $columnFrom, int $rowTo, int $columnTo): array { ( $rowFrom >= 0 - and $rowTo <= count($this->matrix) - and $columnFrom >= 0 - and $columnTo <= count($this->matrix[0]) - and $rowFrom <= $rowTo - and $columnFrom <= $columnTo + and $rowTo <= count($this->matrix) + and $columnFrom >= 0 + and $columnTo <= count($this->matrix[0]) + and $rowFrom <= $rowTo + and $columnFrom <= $columnTo ) - or throw new InvalidArgumentException('Invalid range'); + or throw new InvalidArgumentException('Invalid range'); $result = []; for ($i = $rowFrom; $i <= $rowTo; $i++) { @@ -758,10 +978,11 @@ public function range(int $rowFrom, int $columnFrom, int $rowTo, int $columnTo): /** * Group by a column * - * @param int $column + * @param array $columns + * @param \Closure $aggregate * @param bool $firstRowAsHeaders * - * @return array + * @return array>> */ public function groupBy(array $columns, \Closure $aggregate = null, bool $firstRowAsHeaders = false): array { @@ -769,7 +990,7 @@ public function groupBy(array $columns, \Closure $aggregate = null, bool $firstR $result = []; if ($firstRowAsHeaders) { - $headers = array_shift($data); + array_shift($data); } foreach ($data as $row) { @@ -794,22 +1015,32 @@ public function groupBy(array $columns, \Closure $aggregate = null, bool $firstR return $result; } - public function getTotalRows() + /** + * Get the total rows + * + * @return int + */ + public function getTotalRows(): int { return count($this->matrix); } - public function getTotalColumns() + /** + * Get the total columns + * + * @return int + */ + public function getTotalColumns(): int { return count($this->matrix[0]); - } + } /** * Get a row * * @param int $row * - * @return array + * @return array */ public function getRow(int $row): array { @@ -821,7 +1052,7 @@ public function getRow(int $row): array * * @param int $column * - * @return array + * @return array */ public function getColumn(int $column): array { @@ -837,7 +1068,7 @@ public function getColumn(int $column): array * * @param int $row * - * @return array + * @return array */ public function getRowWithFormulas(int $row): array { @@ -849,7 +1080,7 @@ public function getRowWithFormulas(int $row): array * * @param int $column * - * @return array + * @return array */ public function getColumnWithFormulas(int $column): array { @@ -919,4 +1150,218 @@ public function __toString() { return $this->format($this->defaultFormat); } + + /** + * Magic method to get a property + * + * @param string $name + * + * @return mixed + */ + public function __get($name): mixed + { + if ($name == "rows") { + return $this->getTotalRows(); + } + + if ($name == "columns") { + return $this->getTotalColumns(); + } + + if (is_numeric($name)) { + $pos = $name * 1; + $name = explode(".", $name); + $row = intval($name[0]); + $column = intval($name[1] ?? 0); + + if ($pos < 0) { + $row = $this->getTotalRows() + $row; + } + + return $this->get($row, $column); + } + + if (property_exists($this, $name)) { + return $this->$name; + } + + throw new InvalidArgumentException('Invalid property'); + } + + /** + * Magic method to set a property + * + * @param string $name + * @param mixed $value + * + * @return void + */ + public function __set($name, $value): void + { + if (is_numeric($name)) { + $pos = $name * 1; + $name = explode(".", $name); + $row = intval($name[0]); + $column = intval($name[1] ?? 0); + + if ($pos < 0) { + $row = $this->getTotalRows() + $row; + } + + $this->set($row, $column, $value); + return; + } + + if (property_exists($this, $name)) { + $this->$name = $value; + return; + } + + throw new InvalidArgumentException('Invalid property'); + } + + /** + * Insert a row before a row + * + * @param int $row + * @param array $data + * + * @throws InvalidArgumentException + * + * @return void + */ + public function insertBeforeRow(int $row, array $data): void + { + if (!$this->validateRow($data)) { + throw new InvalidArgumentException('Row does not have the same number of elements as the first row'); + } + + array_splice($this->matrix, $row, 0, [$data]); + array_splice($this->matrix_original, $row, 0, [$data]); + + self::evaluateAll(); + } + + /** + * Insert a row after a row + * + * @param int $row + * @param array $data + * + * @throws InvalidArgumentException + * + * @return void + */ + public function insertAfterRow(int $row, array $data): void + { + if (!$this->validateRow($data)) { + throw new InvalidArgumentException('Row does not have the same number of elements as the first row'); + } + + array_splice($this->matrix, $row + 1, 0, [$data]); + array_splice($this->matrix_original, $row + 1, 0, [$data]); + + self::evaluateAll(); + } + + /** + * Insert a column before a column + * + * @param int $column + * @param mixed $value + * + * @return void + */ + public function insertBeforeColumn(int $column, mixed $value = null): void + { + if (!is_array($value)) { + $value = array_fill(0, count($this->matrix), $value); + $value = (array) $value; + } + + $i = 0; + foreach ($this->matrix as $rowIndex => $row) { + array_splice($this->matrix[$rowIndex], $column, 0, [$value[$i]]); + array_splice($this->matrix_original[$rowIndex], $column, 0, [$value[$i]]); + $i++; + } + + self::evaluateAll(); + } + + /** + * Insert a column after a column + * + * @param int $column + * @param mixed $value + * + * @return void + */ + public function insertAfterColumn(int $column, $value = null): void + { + if (!is_array($value)) { + $value = array_fill(0, count($this->matrix), $value); + $value = (array) $value; + } + + $i = 0; + foreach ($this->matrix as $rowIndex => $row) { + array_splice($this->matrix[$rowIndex], $column + 1, 0, [$value[$i]]); + array_splice($this->matrix_original[$rowIndex], $column + 1, 0, [$value[$i]]); + $i++; + } + + self::evaluateAll(); + } + + /** + * Remove a column + * + * @param int $column + * + * @return void + */ + public function removeColumn(int $column): void + { + foreach ($this->matrix as $rowIndex => $row) { + array_splice($this->matrix[$rowIndex], $column, 1); + array_splice($this->matrix_original[$rowIndex], $column, 1); + } + + self::evaluateAll(); + } + + /** + * Remove a column + * + * @param int $from + * @param int $to + * + * @return void + */ + public function removeColumnRange(int $from, int $to): void + { + foreach ($this->matrix as $rowIndex => $row) { + array_splice($this->matrix[$rowIndex], $from, $to - $from + 1); + array_splice($this->matrix_original[$rowIndex], $from, $to - $from + 1); + } + + self::evaluateAll(); + } + + /** + * Remove a row + * + * @param int $from + * @param int $to + * + * @return void + */ + public function removeRowRange(int $from, int $to): void + { + array_splice($this->matrix, $from, $to - $from + 1); + array_splice($this->matrix_original, $from, $to - $from + 1); + + self::evaluateAll(); + } }