Skip to content

Enable most of the Date/Time functions to accept array arguments #2573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 77 additions & 17 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -575,21 +575,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php

-
message: "#^Binary operation \"/\" between int\\|string and \\(float\\|int\\) results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php

-
message: "#^Binary operation \"/\" between int\\|string and 360 results in an error\\.$#"
count: 3
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php

-
message: "#^Binary operation \"/\" between int\\|string and 365 results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engine\\\\Logger\\:\\:writeDebugLog\\(\\) has parameter \\$args with no type specified\\.$#"
count: 1
Expand Down Expand Up @@ -870,6 +855,11 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/Financial.php

-
message: "#^Parameter \\#1 \\$year of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:isLeapYear\\(\\) expects int\\|string, array\\|int\\|string given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Amortization.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has no return type specified\\.$#"
count: 1
Expand Down Expand Up @@ -925,11 +915,21 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xnpvOrdered\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\Periodic\\:\\:presentValue\\(\\) has parameter \\$args with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php

-
message: "#^Parameter \\#1 \\$year of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Helpers\\:\\:daysPerYear\\(\\) expects int\\|string, array\\|int\\|string given\\.$#"
count: 3
path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateCost\\(\\) has parameter \\$cost with no type specified\\.$#"
count: 1
Expand Down Expand Up @@ -975,11 +975,71 @@ parameters:
count: 2
path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\AccruedInterest\\:\\:atMaturity\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\AccruedInterest\\:\\:periodic\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 2
path: src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php

-
message: "#^Binary operation \"/\" between float\\|string and float\\|string results in an error\\.$#"
count: 2
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Price\\:\\:priceAtMaturity\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 3
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Price\\:\\:priceDiscounted\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Price\\:\\:received\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php

-
message: "#^Parameter \\#1 \\$year of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Helpers\\:\\:daysPerYear\\(\\) expects int\\|string, array\\|int\\|string given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Rates\\:\\:discount\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Rates\\:\\:interest\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Yields\\:\\:yieldAtMaturity\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 3
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php

-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Securities\\\\Yields\\:\\:yieldDiscounted\\(\\) should return float\\|string but returns array\\|string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php

-
message: "#^Parameter \\#1 \\$year of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Helpers\\:\\:daysPerYear\\(\\) expects int\\|string, array\\|int\\|string given\\.$#"
count: 2
path: src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php

-
message: "#^Parameter \\#1 \\$year of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Helpers\\:\\:daysPerYear\\(\\) expects int\\|string, array\\|int\\|string given\\.$#"
count: 3
path: src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php

-
message: "#^Cannot call method getTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
count: 4
Expand Down Expand Up @@ -1916,7 +1976,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/TextData/Format.php

-
message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\DateValue\\:\\:fromString\\(\\) expects string, mixed given\\.$#"
message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\DateValue\\:\\:fromString\\(\\) expects array\\|string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/TextData/Format.php

Expand All @@ -1931,7 +1991,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/TextData/Format.php

-
message: "#^Parameter \\#1 \\$timeValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\TimeValue\\:\\:fromString\\(\\) expects string, mixed given\\.$#"
message: "#^Parameter \\#1 \\$timeValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\TimeValue\\:\\:fromString\\(\\) expects array\\|string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/TextData/Format.php

Expand Down
150 changes: 8 additions & 142 deletions src/PhpSpreadsheet/Calculation/ArrayEnabled.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation;

use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor;

trait ArrayEnabled
{
Expand Down Expand Up @@ -37,154 +38,19 @@ protected static function evaluateArrayArguments(callable $method, ...$arguments
self::initialiseHelper($arguments);
$arguments = self::$arrayArgumentHelper->arguments();

if (self::$arrayArgumentHelper->hasArrayArgument() === false) {
return [$method(...$arguments)];
}

if (self::$arrayArgumentHelper->arrayArguments() === 1) {
$nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber();

return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments);
}

$singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
$singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
// Basic logic for a single row vector and a single column vector
return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
}

$matrixPair = self::$arrayArgumentHelper->getMatrixPair();
if ($matrixPair !== []) {
if (
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === true &&
self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) ||
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === false &&
self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
) {
// Logic for a matrix and a vector (row or column)
return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
}
// Logic for matrix/matrix, column vector/column vector or row vector/row vector
return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
}

// Still need to work out the logic for more than two array arguments,
// For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper
return ['#VALUE!'];
}

/**
* @param mixed ...$arguments
*/
private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];

$rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
$columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));

if ($rows === 1) {
$rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
}
if ($columns === 1) {
$columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
}

$result = [];
for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
$rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
$columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
$value1 = $matrixValues1[$rowIndex1][$columnIndex1];
$rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
$columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
$value2 = $matrixValues2[$rowIndex2][$columnIndex2];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;

$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}

return $result;
}

/**
* @param mixed ...$arguments
*/
private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];

$result = [];
foreach ($matrixValues1 as $rowIndex => $row) {
foreach ($row as $columnIndex => $value1) {
if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
continue;
}

$value2 = $matrixValues2[$rowIndex][$columnIndex];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;

$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}

return $result;
}

/**
* @param mixed ...$arguments
*/
private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array
{
$rowVector = Functions::flattenArray($arguments[$rowIndex]);
$columnVector = Functions::flattenArray($arguments[$columnIndex]);

$result = [];
foreach ($columnVector as $column) {
$rowResults = [];
foreach ($rowVector as $row) {
$arguments[$rowIndex] = $row;
$arguments[$columnIndex] = $column;

$rowResults[] = $method(...$arguments);
}
$result[] = $rowResults;
}

return $result;
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}

/**
* Note, offset is from 1 (for the first argument) rather than from 0.
*
* @param mixed ...$arguments
*/
private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array
protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array
{
$values = array_slice($arguments, $nthArgument - 1, 1);
/** @var array $values */
$values = array_pop($values);

$result = [];
foreach ($values as $value) {
$arguments[$nthArgument - 1] = $value;
$result[] = $method(...$arguments);
}
self::initialiseHelper(array_slice($arguments, 0, $limit));
$trailingArguments = array_slice($arguments, $limit);
$arguments = self::$arrayArgumentHelper->arguments();
$arguments = array_merge($arguments, $trailingArguments);

return $result;
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
}
6 changes: 6 additions & 0 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4368,6 +4368,12 @@ private static function dataTestReference(&$operandData)
if (($operandData['reference'] === null) && (is_array($operand))) {
$rKeys = array_keys($operand);
$rowKey = array_shift($rKeys);
if (is_array($operand[$rowKey]) === false) {
$operandData['value'] = $operand[$rowKey];

return $operand[$rowKey];
}

$cKeys = array_keys(array_keys($operand[$rowKey]));
$colKey = array_shift($cKeys);
if (ctype_upper("$colKey")) {
Expand Down
Loading