BREAKING `Worksheet::getCell()` cannot return null anymore

`Worksheet::getCell()` used to optionnaly return null if passed a
second argument. This second argument was removed entirely and the
method always returns a Cell (possibly creating it if needed).

This make the API more predictable and easier to do static analysis
with tools such as PHPStan.

If you relied on that second parameter, you should instead use the
`Worksheet::cellExists()` before calling `getCell()`.
This commit is contained in:
Adrien Crivelli 2021-04-13 10:11:59 +09:00
parent a34695e0f9
commit d85eaacfa3
No known key found for this signature in database
GPG Key ID: 16D79B903B4B5874
4 changed files with 81 additions and 2017 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5334,9 +5334,7 @@ class Calculation
$recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $pCellWorksheet)
? $definedNameWorksheet->getCell('A1')
: $pCell;
$recursiveCalculationCellAddress = $recursiveCalculationCell !== null
? $recursiveCalculationCell->getCoordinate()
: null;
$recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();
// Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns
$definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(

View File

@ -730,7 +730,7 @@ class Worksheet implements IComparable
// loop through all cells in the worksheet
foreach ($this->getCoordinates(false) as $coordinate) {
$cell = $this->getCell($coordinate, false);
$cell = $this->getCellOrNull($coordinate);
if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
//Determine if cell is in merge range
$isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
@ -1168,52 +1168,86 @@ class Worksheet implements IComparable
/**
* Get cell at a specific coordinate.
*
* @param string $pCoordinate Coordinate of the cell, eg: 'A1'
* @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
* already exist, or a null should be returned instead
* @param string $coordinate Coordinate of the cell, eg: 'A1'
*
* @return null|Cell Cell that was found/created or null
* @return Cell Cell that was found or created
*/
public function getCell($pCoordinate, $createIfNotExists = true)
public function getCell(string $coordinate): Cell
{
// Uppercase coordinate
$pCoordinateUpper = strtoupper($pCoordinate);
/** @var Worksheet $sheet */
[$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate);
$cell = $sheet->cellCollection->get($finalCoordinate);
// Check cell collection
if ($this->cellCollection->has($pCoordinateUpper)) {
return $this->cellCollection->get($pCoordinateUpper);
return $cell ?? $sheet->createNewCell($finalCoordinate);
}
/**
* Get the correct Worksheet and coordinate from a coordinate that may
* contains reference to another sheet or a named range.
*
* @return array{0: Worksheet, 1: string}
*/
private function getWorksheetAndCoordinate(string $pCoordinate): array
{
$sheet = null;
$finalCoordinate = null;
// Worksheet reference?
if (strpos($pCoordinate, '!') !== false) {
$worksheetReference = self::extractSheetTitle($pCoordinate, true);
return $this->parent->getSheetByName($worksheetReference[0])
->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
}
$sheet = $this->parent->getSheetByName($worksheetReference[0]);
$finalCoordinate = strtoupper($worksheetReference[1]);
// Named range?
if (
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
(preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate, $matches))
if (!$sheet) {
throw new Exception('Sheet not found for name: ' . $worksheetReference[0]);
}
} elseif (
!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate) &&
preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate)
) {
// Named range?
$namedRange = $this->validateNamedRange($pCoordinate, true);
if ($namedRange !== null) {
$sheet = $namedRange->getWorksheet();
if (!$sheet) {
throw new Exception('Sheet not found for named range: ' . $namedRange->getName());
}
$cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!');
$cellCoordinate = str_replace('$', '', $cellCoordinate);
return $namedRange->getWorksheet()->getCell($cellCoordinate, $createIfNotExists);
$finalCoordinate = str_replace('$', '', $cellCoordinate);
}
}
if (Coordinate::coordinateIsRange($pCoordinate)) {
throw new Exception('Cell coordinate can not be a range of cells.');
} elseif (strpos($pCoordinate, '$') !== false) {
if (!$sheet || !$finalCoordinate) {
$sheet = $this;
$finalCoordinate = strtoupper($pCoordinate);
}
if (Coordinate::coordinateIsRange($finalCoordinate)) {
throw new Exception('Cell coordinate string can not be a range of cells.');
} elseif (strpos($finalCoordinate, '$') !== false) {
throw new Exception('Cell coordinate must not be absolute.');
}
// Create new cell object, if required
return $createIfNotExists ? $this->createNewCell($pCoordinateUpper) : null;
return [$sheet, $finalCoordinate];
}
/**
* Get an existing cell at a specific coordinate, or null.
*
* @param string $coordinate Coordinate of the cell, eg: 'A1'
*
* @return null|Cell Cell that was found or null
*/
private function getCellOrNull($coordinate): ?Cell
{
// Check cell collection
if ($this->cellCollection->has($coordinate)) {
return $this->cellCollection->get($coordinate);
}
return null;
}
/**
@ -1281,44 +1315,16 @@ class Worksheet implements IComparable
/**
* Does the cell at a specific coordinate exist?
*
* @param string $pCoordinate Coordinate of the cell eg: 'A1'
* @param string $coordinate Coordinate of the cell eg: 'A1'
*
* @return bool
*/
public function cellExists($pCoordinate)
public function cellExists($coordinate)
{
// Worksheet reference?
if (strpos($pCoordinate, '!') !== false) {
$worksheetReference = self::extractSheetTitle($pCoordinate, true);
/** @var Worksheet $sheet */
[$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate);
return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
}
// Named range?
if (
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
(preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate, $matches))
) {
$namedRange = $this->validateNamedRange($pCoordinate, true);
if ($namedRange !== null) {
$cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!');
$cellCoordinate = str_replace('$', '', $cellCoordinate);
return $namedRange->getWorksheet()->cellExists($cellCoordinate);
}
}
// Uppercase coordinate
$pCoordinate = strtoupper($pCoordinate);
if (Coordinate::coordinateIsRange($pCoordinate)) {
throw new Exception('Cell coordinate can not be a range of cells.');
} elseif (strpos($pCoordinate, '$') !== false) {
throw new Exception('Cell coordinate must not be absolute.');
}
// Cell exists?
return $this->cellCollection->has($pCoordinate);
return $sheet->cellCollection->has($finalCoordinate);
}
/**

View File

@ -55,7 +55,7 @@ class WorksheetNamedRangesTest extends TestCase
$namedRange = 'Range1';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Cell coordinate can not be a range of cells');
$this->expectExceptionMessage('Cell coordinate string can not be a range of cells');
$worksheet = $this->spreadsheet->getActiveSheet();
$worksheet->cellExists($namedRange);
@ -118,7 +118,7 @@ class WorksheetNamedRangesTest extends TestCase
$namedCell = 'Range1';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Cell coordinate can not be a range of cells');
$this->expectExceptionMessage('Cell coordinate string can not be a range of cells');
$worksheet = $this->spreadsheet->getActiveSheet();
$worksheet->getCell($namedCell);