Better Definitions for Mixed Parameters and Values Part 2 of Many

Continuing work started with PR #4019. Improve documentation within program by making explicit what types of values are allowed for variables described as "mixed". In order to avoid broken functionality, this is done mainly through doc-blocks. This will get us closer to Phpstan Level 9, but many changes will be needed before we can consider that.

This change has more executable code changes than its predecessor. I will wait longer than normal before merging it to allow for additional testing.
This commit is contained in:
oleibman
2024-05-12 17:10:46 -07:00
parent 563de5f3da
commit a7378be4aa
20 changed files with 104 additions and 62 deletions
+12 -6
View File
@@ -288,7 +288,7 @@ class Cells
$newCollection->index[$key] = $value;
$stored = $newCollection->cache->set(
$newCollection->cachePrefix . $key,
clone $this->cache->get($this->cachePrefix . $key)
clone $this->getCache($key)
);
if ($stored === false) {
$this->destructIfNeeded($newCollection, 'Failed to copy cells in cache');
@@ -410,11 +410,7 @@ class Cells
return null;
}
// Check if the entry that has been requested actually exists in the cache
$cell = $this->cache->get($this->cachePrefix . $cellCoordinate);
if ($cell === null) {
throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else.");
}
$cell = $this->getcache($cellCoordinate);
// Set current entry to the requested entry
$this->currentCoordinate = $cellCoordinate;
@@ -466,4 +462,14 @@ class Cells
yield $this->cachePrefix . $coordinate;
}
}
private function getCache(string $cellCoordinate): Cell
{
$cell = $this->cache->get($this->cachePrefix . $cellCoordinate);
if (!($cell instanceof Cell)) {
throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else.");
}
return $cell;
}
}
+2
View File
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Helper;
use DOMAttr;
use DOMDocument;
use DOMElement;
use DOMNode;
@@ -708,6 +709,7 @@ class Html
{
$attrs = $tag->attributes;
if ($attrs !== null) {
/** @var DOMAttr $attribute */
foreach ($attrs as $attribute) {
$attributeName = strtolower($attribute->name);
$attributeName = preg_replace('/^html:/', '', $attributeName) ?? $attributeName; // in case from Xml spreadsheet
+4 -8
View File
@@ -81,13 +81,9 @@ class Sample
$regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
$files = [];
/** @var string[] $file */
foreach ($regex as $file) {
$file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
if (is_array($file)) {
// @codeCoverageIgnoreStart
throw new RuntimeException('str_replace returned array');
// @codeCoverageIgnoreEnd
}
$info = pathinfo($file);
$category = str_replace('_', ' ', $info['dirname'] ?? '');
$name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
@@ -254,10 +250,10 @@ class Sample
?string $descriptionCell = null
): void {
if ($descriptionCell !== null) {
$this->log($worksheet->getCell($descriptionCell)->getValue());
$this->log($worksheet->getCell($descriptionCell)->getValueString());
}
$this->log($worksheet->getCell($formulaCell)->getValue());
$this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue());
$this->log($worksheet->getCell($formulaCell)->getValueString());
$this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValueString());
}
/**
+2
View File
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader;
use DOMAttr;
use DOMDocument;
use DOMElement;
use DOMNode;
@@ -291,6 +292,7 @@ class Html extends BaseReader
private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
{
$attributeArray = [];
/** @var DOMAttr $attribute */
foreach ($child->attributes as $attribute) {
$attributeArray[$attribute->name] = $attribute->value;
}
+2
View File
@@ -268,6 +268,7 @@ class Slk extends BaseReader
if ($sharedFormula === true && $sharedRow >= 0 && $sharedColumn >= 0) {
$thisCoordinate = Coordinate::stringFromColumnIndex((int) $column) . $row;
$sharedCoordinate = Coordinate::stringFromColumnIndex($sharedColumn) . $sharedRow;
/** @var string */
$formula = $spreadsheet->getActiveSheet()->getCell($sharedCoordinate)->getValue();
$spreadsheet->getActiveSheet()->getCell($thisCoordinate)->setValue($formula);
$referenceHelper = ReferenceHelper::getInstance();
@@ -281,6 +282,7 @@ class Slk extends BaseReader
return;
}
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
/** @var string */
$cellData = Calculation::unwrapResult($cellData);
// Set cell value
@@ -32,6 +32,8 @@ class SpgrContainer
/**
* Add a child. This will be either spgrContainer or spContainer.
*
* @param SpgrContainer|SpgrContainer\SpContainer $child child to be added
*/
public function addChild(mixed $child): void
{
+1 -1
View File
@@ -253,7 +253,7 @@ class Spreadsheet implements JsonSerializable
*/
public function setRibbonXMLData(mixed $target, mixed $xmlData): void
{
if ($target !== null && $xmlData !== null) {
if (is_string($target) && is_string($xmlData)) {
$this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
} else {
$this->ribbonXMLData = null;
@@ -235,7 +235,7 @@ class CellMatcher
self::COMPARISON_DUPLICATES_OPERATORS[$conditional->getConditionType()],
$worksheetName,
$this->conditionalRange,
$this->cellConditionCheck($this->cell->getCalculatedValue())
$this->cellConditionCheck($this->cell->getCalculatedValueString())
);
return $this->evaluateExpression($expression);
@@ -69,6 +69,7 @@ class CellValue extends WizardAbstract implements WizardInterface
$this->operandValueType[$index] = $operandValueType;
}
/** @param null|bool|float|int|string $value value to be wrapped */
protected function wrapValue(mixed $value, string $operandValueType): float|int|string
{
if (!is_numeric($value) && !is_bool($value) && null !== $value) {
@@ -175,7 +176,9 @@ class CellValue extends WizardAbstract implements WizardInterface
if (count($arguments) < 2) {
$this->operand(0, $arguments[0]);
} else {
$this->operand(0, $arguments[0], $arguments[1]);
/** @var string */
$arg1 = $arguments[1];
$this->operand(0, $arguments[0], $arg1);
}
return $this;
@@ -54,7 +54,7 @@ class Expression extends WizardAbstract implements WizardInterface
}
/**
* @param mixed[] $arguments
* @param string[] $arguments
*/
public function __call(string $methodName, array $arguments): self
{
@@ -148,9 +148,15 @@ class TextValue extends WizardAbstract implements WizardInterface
$this->operator(self::MAGIC_OPERATIONS[$methodName]);
//$this->operand(...$arguments);
if (count($arguments) < 2) {
$this->operand($arguments[0]);
/** @var string */
$arg0 = $arguments[0];
$this->operand($arg0);
} else {
$this->operand($arguments[0], $arguments[1]);
/** @var string */
$arg0 = $arguments[0];
/** @var string */
$arg1 = $arguments[1];
$this->operand($arg0, $arg1);
}
return $this;
@@ -115,6 +115,7 @@ class DateFormatter
}
}
/** @param float|int $value value to be formatted */
public static function format(mixed $value, string $format): string
{
// strip off first part containing e.g. [$-F800] or [$USD-409]
@@ -185,6 +185,7 @@ class NumberFormatter extends BaseFormatter
return self::pregReplace(self::NUMBER_REGEX, $value, $format);
}
/** @param float|int|numeric-string $value value to be formatted */
public static function format(mixed $value, string $format): string
{
// The "_" in this string has already been stripped out,
+12 -6
View File
@@ -309,7 +309,7 @@ class AutoFilter implements Stringable
/**
* Test if cell value is in the defined set of values.
*
* @param mixed[] $dataSet
* @param array{blanks: bool, filterValues: array<string,array<string,string>>} $dataSet
*/
protected static function filterTestInSimpleDataSet(mixed $cellValue, array $dataSet): bool
{
@@ -325,7 +325,7 @@ class AutoFilter implements Stringable
/**
* Test if cell value is in the defined set of Excel date values.
*
* @param mixed[] $dataSet
* @param array{blanks: bool, filterValues: array<string,array<string,string>>} $dataSet
*/
protected static function filterTestInDateGroupSet(mixed $cellValue, array $dataSet): bool
{
@@ -763,9 +763,13 @@ class AutoFilter implements Stringable
sort($dataValues);
}
$slice = array_slice($dataValues, 0, $ruleValue);
$retVal = array_pop($slice);
if (is_numeric($ruleValue)) {
$ruleValue = (int) $ruleValue;
}
if ($ruleValue === null || is_int($ruleValue)) {
$slice = array_slice($dataValues, 0, $ruleValue);
$retVal = array_pop($slice);
}
}
return $retVal;
@@ -968,7 +972,7 @@ class AutoFilter implements Stringable
$ruleOperator = $rule->getOperator();
}
if (is_numeric($ruleValue) && $ruleOperator === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) {
$ruleValue = floor((float) $ruleValue * ($dataRowCount / 100));
$ruleValue = (int) floor((float) $ruleValue * ($dataRowCount / 100));
}
if (!is_array($ruleValue) && $ruleValue < 1) {
$ruleValue = 1;
@@ -977,6 +981,7 @@ class AutoFilter implements Stringable
$ruleValue = 500;
}
/** @var float|int|string */
$maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, (int) $rangeEnd[1], $toptenRuleType, $ruleValue);
$operator = ($toptenRuleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP)
@@ -1003,6 +1008,7 @@ class AutoFilter implements Stringable
// Execute the filter test
/** @var callable */
$temp = [self::class, $columnFilterTest['method']];
/** @var bool */
$result // $result && // phpstan says $result is always true here
= call_user_func_array($temp, [$cellValue, $columnFilterTest['arguments']]);
// If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests
@@ -76,7 +76,7 @@ class Column
/**
* Autofilter Column Dynamic Attributes.
*
* @var mixed[]
* @var (float|int|string)[]
*/
private array $attributes = [];
@@ -209,7 +209,7 @@ class Column
/**
* Set AutoFilter Attributes.
*
* @param mixed[] $attributes
* @param (float|int|string)[] $attributes
*
* @return $this
*/
@@ -225,7 +225,7 @@ class Column
* Set An AutoFilter Attribute.
*
* @param string $name Attribute Name
* @param int|string $value Attribute Value
* @param float|int|string $value Attribute Value
*
* @return $this
*/
@@ -240,7 +240,7 @@ class Column
/**
* Get AutoFilter Column Attributes.
*
* @return int[]|string[]
* @return (float|int|string)[]
*/
public function getAttributes(): array
{
@@ -252,7 +252,7 @@ class Column
*
* @param string $name Attribute Name
*/
public function getAttribute(string $name): null|int|string
public function getAttribute(string $name): null|float|int|string
{
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
@@ -360,6 +360,7 @@ class Column
public function __clone()
{
$vars = get_object_vars($this);
/** @var AutoFilter\Column\Rule[] $value */
foreach ($vars as $key => $value) {
if ($key === 'parent') {
// Detach from autofilter parent
+12 -7
View File
@@ -745,7 +745,7 @@ class Worksheet implements IComparable
// Calculated value
// To formatted string
$cellValue = NumberFormat::toFormattedString(
$cell->getCalculatedValue(),
$cell->getCalculatedValueString(),
(string) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())
->getNumberFormat()->getFormatCode(true)
);
@@ -2795,6 +2795,8 @@ class Worksheet implements IComparable
}
/**
* @param null|bool|float|int|RichText|string $nullValue value to use when null
*
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception
*/
@@ -2811,8 +2813,10 @@ class Worksheet implements IComparable
if ($formatData) {
$style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
/** @var null|bool|float|int|RichText|string */
$returnValuex = $returnValue;
$returnValue = NumberFormat::toFormattedString(
$returnValue,
$returnValuex,
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
);
}
@@ -2824,7 +2828,7 @@ class Worksheet implements IComparable
/**
* Create array from a range of cells.
*
* @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
* @param null|bool|float|int|RichText|string $nullValue Value returned in the array entry if a cell doesn't exist
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
@@ -2854,7 +2858,7 @@ class Worksheet implements IComparable
/**
* Create array from a range of cells, yielding each row in turn.
*
* @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
* @param null|bool|float|int|RichText|string $nullValue Value returned in the array entry if a cell doesn't exist
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
@@ -3002,7 +3006,7 @@ class Worksheet implements IComparable
* Create array from a range of cells.
*
* @param string $definedName The Named Range that should be returned
* @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
* @param null|bool|float|int|RichText|string $nullValue Value returned in the array entry if a cell doesn't exist
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
@@ -3035,7 +3039,7 @@ class Worksheet implements IComparable
/**
* Create array from worksheet.
*
* @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
* @param null|bool|float|int|RichText|string $nullValue Value returned in the array entry if a cell doesn't exist
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
@@ -3650,13 +3654,14 @@ class Worksheet implements IComparable
{
$toArray = Coordinate::extractAllCellReferencesInRange($toCells);
$value = $this->getCell($fromCell)->getValue();
$valueString = $this->getCell($fromCell)->getValueString();
$style = $this->getStyle($fromCell)->exportArray();
$fromIndexes = Coordinate::indexesFromString($fromCell);
$referenceHelper = ReferenceHelper::getInstance();
foreach ($toArray as $destination) {
if ($destination !== $fromCell) {
$toIndexes = Coordinate::indexesFromString($destination);
$this->getCell($destination)->setValue($referenceHelper->updateFormulaReferences($value, 'A1', $toIndexes[0] - $fromIndexes[0], $toIndexes[1] - $fromIndexes[1]));
$this->getCell($destination)->setValue($referenceHelper->updateFormulaReferences($valueString, 'A1', $toIndexes[0] - $fromIndexes[0], $toIndexes[1] - $fromIndexes[1]));
if ($copyStyle) {
$this->getCell($destination)->getStyle()->applyFromArray($style);
}
@@ -35,7 +35,9 @@ class ConditionalHelper
$this->tokens = pack('Cv', 0x1E, $condition);
} else {
try {
$formula = Wizard\WizardAbstract::reverseAdjustCellRef((string) $condition, $cellRange);
/** @var float|int|string */
$conditionx = $condition;
$formula = Wizard\WizardAbstract::reverseAdjustCellRef((string) $conditionx, $cellRange);
$this->parser->parse($formula);
$this->tokens = $this->parser->toReversePolish();
$this->size = strlen($this->tokens ?? '');
+3 -3
View File
@@ -542,18 +542,18 @@ class Parser
/**
* Convert a number token to ptgInt or ptgNum.
*
* @param mixed $num an integer or double for conversion to its ptg value
* @param float|int|string $num an integer or double for conversion to its ptg value
*/
private function convertNumber(mixed $num): string
{
// Integer in the range 0..2**16-1
if ((preg_match('/^\\d+$/', $num)) && ($num <= 65535)) {
if ((preg_match('/^\\d+$/', (string) $num)) && ($num <= 65535)) {
return pack('Cv', $this->ptg['ptgInt'], $num);
}
// A float
if (BIFFwriter::getByteOrder()) { // if it's Big Endian
$num = strrev($num);
$num = strrev((string) $num);
}
return pack('Cd', $this->ptg['ptgNum'], $num);
+11 -11
View File
@@ -382,37 +382,37 @@ class Worksheet extends BIFFwriter
if ($cVal === '' || $cVal === null) {
$this->writeBlank($row, $column, $xfIndex);
} else {
$this->writeString($row, $column, $cVal, $xfIndex);
$this->writeString($row, $column, $cell->getValueString(), $xfIndex);
}
break;
case DataType::TYPE_NUMERIC:
$this->writeNumber($row, $column, $cVal, $xfIndex);
$this->writeNumber($row, $column, is_numeric($cVal) ? ($cVal + 0) : 0, $xfIndex);
break;
case DataType::TYPE_FORMULA:
$calculatedValue = $this->preCalculateFormulas
? $cell->getCalculatedValue() : null;
if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) {
$calculatedValue = $this->preCalculateFormulas ? $cell->getCalculatedValue() : null;
$calculatedValueString = $this->preCalculateFormulas ? $cell->getCalculatedValueString() : '';
if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cell->getValueString(), $xfIndex, $calculatedValue)) {
if ($calculatedValue === null) {
$calculatedValue = $cell->getCalculatedValue();
}
$calctype = gettype($calculatedValue);
match ($calctype) {
'integer', 'double' => $this->writeNumber($row, $column, (float) $calculatedValue, $xfIndex),
'string' => $this->writeString($row, $column, $calculatedValue, $xfIndex),
'boolean' => $this->writeBoolErr($row, $column, (int) $calculatedValue, 0, $xfIndex),
default => $this->writeString($row, $column, $cVal, $xfIndex),
'integer', 'double' => $this->writeNumber($row, $column, is_numeric($calculatedValue) ? ((float) $calculatedValue) : 0.0, $xfIndex),
'string' => $this->writeString($row, $column, $calculatedValueString, $xfIndex),
'boolean' => $this->writeBoolErr($row, $column, (int) $calculatedValueString, 0, $xfIndex),
default => $this->writeString($row, $column, $cell->getValueString(), $xfIndex),
};
}
break;
case DataType::TYPE_BOOL:
$this->writeBoolErr($row, $column, $cVal, 0, $xfIndex);
$this->writeBoolErr($row, $column, (int) $cell->getValueString(), 0, $xfIndex);
break;
case DataType::TYPE_ERROR:
$this->writeBoolErr($row, $column, ErrorCode::error($cVal), 1, $xfIndex);
$this->writeBoolErr($row, $column, ErrorCode::error($cell->getValueString()), 1, $xfIndex);
break;
}
+16 -9
View File
@@ -1399,6 +1399,7 @@ class Worksheet extends WriterPart
private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void
{
$calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $cell->getCalculatedValue() : $cellValue;
$calculatedValueString = $this->getParentWriter()->getPreCalculateFormulas() ? $cell->getCalculatedValueString() : $cellValue;
if (is_string($calculatedValue)) {
if (ErrorValue::isError($calculatedValue)) {
$this->writeCellError($objWriter, 'e', $cellValue, $calculatedValue);
@@ -1407,13 +1408,15 @@ class Worksheet extends WriterPart
}
$objWriter->writeAttribute('t', 'str');
$calculatedValue = StringHelper::controlCharacterPHP2OOXML($calculatedValue);
$calculatedValueString = $calculatedValue;
} elseif (is_bool($calculatedValue)) {
$objWriter->writeAttribute('t', 'b');
$calculatedValue = (int) $calculatedValue;
$calculatedValueString = (string) $calculatedValue;
}
$attributes = $cell->getFormulaAttributes();
if (($attributes['t'] ?? null) === 'array') {
if (is_array($attributes) && ($attributes['t'] ?? null) === 'array') {
$objWriter->startElement('f');
$objWriter->writeAttribute('t', 'array');
$objWriter->writeAttribute('ref', $cell->getCoordinate());
@@ -1429,8 +1432,8 @@ class Worksheet extends WriterPart
&& $this->getParentWriter()->getPreCalculateFormulas()
&& $calculatedValue !== null,
'v',
(!is_array($calculatedValue) && !str_starts_with($calculatedValue ?? '', '#'))
? StringHelper::formatNumber($calculatedValue) : '0'
(!is_array($calculatedValue) && !str_starts_with($calculatedValueString, '#'))
? StringHelper::formatNumber($calculatedValueString) : '0'
);
}
}
@@ -1447,6 +1450,7 @@ class Worksheet extends WriterPart
$pCell = $worksheet->getCell($cellAddress);
$xfi = $pCell->getXfIndex();
$cellValue = $pCell->getValue();
$cellValueString = $pCell->getValueString();
$writeValue = $cellValue !== '' && $cellValue !== null;
if (empty($xfi) && !$writeValue) {
return;
@@ -1469,27 +1473,30 @@ class Worksheet extends WriterPart
// Write data depending on its type
switch (strtolower($mappedType)) {
case 'inlinestr': // Inline string
$this->writeCellInlineStr($objWriter, $mappedType, $cellValue);
/** @var RichText|string */
$richText = $cellValue;
$this->writeCellInlineStr($objWriter, $mappedType, $richText);
break;
case 's': // String
$this->writeCellString($objWriter, $mappedType, $cellValue, $flippedStringTable);
$this->writeCellString($objWriter, $mappedType, $cellValueString, $flippedStringTable);
break;
case 'f': // Formula
$this->writeCellFormula($objWriter, $cellValue, $pCell);
$this->writeCellFormula($objWriter, $cellValueString, $pCell);
break;
case 'n': // Numeric
$this->writeCellNumeric($objWriter, $cellValue);
$cellValueNumeric = is_numeric($cellValue) ? ($cellValue + 0) : 0;
$this->writeCellNumeric($objWriter, $cellValueNumeric);
break;
case 'b': // Boolean
$this->writeCellBoolean($objWriter, $mappedType, $cellValue);
$this->writeCellBoolean($objWriter, $mappedType, (bool) $cellValue);
break;
case 'e': // Error
$this->writeCellError($objWriter, $mappedType, $cellValue);
$this->writeCellError($objWriter, $mappedType, $cellValueString);
}
}