Skip to content

Commit 62238bc

Browse files
author
MarkBaker
committed
Initial work on deprecating ByColumnAndRow methods; and providing functionality to use the basic cellAddress methods to be used with a string cell address (e.g. C5); an array of columnId and rowId (e.g. [3, 5]) or a new CellAddress object.
Current implementation for all methods that take a single cell reference argument: - `setCellValue()` - `setCellValueExplicit()` - `getCell()` - `cellExists()` - `setBreak()` - `freezePane()` - `getComment()` Also introducing a CellRange object to work with similar cases for methods that accept a cell range rather than simply a cell address; and RowRange/ColumnRange objects for those cases. Still need to apply to methods that accept a cell range or single cell: - `mergeCells()` - `unmergeCells()` - `protectCells()` - `unprotectCells()` - `setAutoFilter()` Then there's a few special cases that accept row and column ranges, not simply cell ranges; or a series of cell ranges.
1 parent 8885c4e commit 62238bc

File tree

10 files changed

+1087
-72
lines changed

10 files changed

+1087
-72
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3737
### Deprecated
3838

3939
- All Excel Function implementations in `Calculation\Functions` (including the Error functions) have been moved to dedicated classes for groups of related functions. See the docblocks against all the deprecated methods for details of the new methods to call instead. At some point, these old classes will be deleted.
40+
- Worksheet methods that reference cells "byColumnandRow". All such methods have an equivalent that references the cell by its address (e.g. '`E3'` rather than `5, 3`).
41+
42+
These functions now accept either a cell address string (`'E3')` or an array with columnId and rowId (`[5, 3]`) or a new `CellAddress` object as their `cellAddress`/`coordinate` argument.
43+
This includes the methods:
44+
- `setCellValueByColumnAndRow()` use the equivalent `setCellValue()`
45+
- `setCellValueExplicitByColumnAndRow()` use the equivalent `setCellValueExplicit()`
46+
- `getCellByColumnAndRow()` use the equivalent `getCell()`
47+
- `cellExistsByColumnAndRow()` use the equivalent `cellExists()`
48+
- `getStyleByColumnAndRow()` use the equivalent `getStyle()`
49+
- `setBreakByColumnAndRow()` use the equivalent `setBreak()`
50+
- `mergeCellsByColumnAndRow()` use the equivalent `mergeCells()`
51+
- `unmergeCellsByColumnAndRow()` use the equivalent `unmergeCells()`
52+
- `protectCellsByColumnAndRow()` use the equivalent `protectCells()`
53+
- `unprotectCellsByColumnAndRow()` use the equivalent `unprotectCells()`
54+
- `setAutoFilterByColumnAndRow()` use the equivalent `setAutoFilter()`
55+
- `freezePaneByColumnAndRow()` use the equivalent `freezePane()`
56+
- `getCommentByColumnAndRow()` use the equivalent `getComment()`
57+
- `setSelectedCellByColumnAndRow()` use the equivalent `setSelectedCells()`
58+
59+
This change provides more consistency in the methods (not every "by cell address" method has an equivalent "byColumnAndRow" method);
60+
and the "by cell address" methods often provide more flexibility, such as allowing a range of cells, or referencig them by passing the defined name of a named range as the argument.
4061

4162
### Removed
4263

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Cell;
4+
5+
use PhpOffice\PhpSpreadsheet\Exception;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
7+
8+
class CellAddress
9+
{
10+
/**
11+
* @var ?Worksheet
12+
*/
13+
protected $worksheet;
14+
15+
/**
16+
* @var string
17+
*/
18+
protected $cellAddress;
19+
20+
/**
21+
* @var string
22+
*/
23+
protected $columnName;
24+
25+
/**
26+
* @var int
27+
*/
28+
protected $columnId;
29+
30+
/**
31+
* @var int
32+
*/
33+
protected $rowId;
34+
35+
protected function __construct(string $cellAddress, ?Worksheet $worksheet)
36+
{
37+
$this->cellAddress = str_replace('$', '', $cellAddress);
38+
[$this->columnName, $rowId] = Coordinate::coordinateFromString($cellAddress);
39+
$this->rowId = (int) $rowId;
40+
$this->columnId = Coordinate::columnIndexFromString($this->columnName);
41+
$this->worksheet = $worksheet;
42+
}
43+
44+
/**
45+
* @param mixed $columnId
46+
* @param mixed $rowId
47+
*/
48+
private static function validateColumnAndRow($columnId, $rowId): void
49+
{
50+
$array = [$columnId, $rowId];
51+
array_walk(
52+
$array,
53+
function ($value): void {
54+
if (!is_numeric($value) || $value <= 0) {
55+
throw new Exception('Row and Column Ids must be positive integer values');
56+
}
57+
}
58+
);
59+
}
60+
61+
/**
62+
* @param mixed $columnId
63+
* @param mixed $rowId
64+
*/
65+
public static function fromColumnAndRow($columnId, $rowId, ?Worksheet $worksheet = null): self
66+
{
67+
self::validateColumnAndRow($columnId, $rowId);
68+
69+
return new static(Coordinate::stringFromColumnIndex($columnId) . ((string) $rowId), $worksheet);
70+
}
71+
72+
public static function fromColumnRowArray(array $array, ?Worksheet $worksheet = null): self
73+
{
74+
[$columnId, $rowId] = $array;
75+
76+
return static::fromColumnAndRow($columnId, $rowId, $worksheet);
77+
}
78+
79+
/**
80+
* @param mixed $cellAddress
81+
*/
82+
public static function fromCellAddress($cellAddress, ?Worksheet $worksheet = null): self
83+
{
84+
return new static($cellAddress, $worksheet);
85+
}
86+
87+
/**
88+
* The returned address string will contain the worksheet name as well, if available,
89+
* (ie. if a Worksheet was provided to the constructor).
90+
* e.g. "'Mark''s Worksheet'!C5".
91+
*/
92+
public function fullCellAddress(): string
93+
{
94+
if ($this->worksheet !== null) {
95+
$title = str_replace("'", "''", $this->worksheet->getTitle());
96+
97+
return "'{$title}'!{$this->cellAddress}";
98+
}
99+
100+
return $this->cellAddress;
101+
}
102+
103+
public function worksheet(): ?Worksheet
104+
{
105+
return $this->worksheet;
106+
}
107+
108+
/**
109+
* The returned address string will contain just the column/row address,
110+
* (even if a Worksheet was provided to the constructor).
111+
* e.g. "C5".
112+
*/
113+
public function cellAddress(): string
114+
{
115+
return $this->cellAddress;
116+
}
117+
118+
public function rowId(): int
119+
{
120+
return $this->rowId;
121+
}
122+
123+
public function columnId(): int
124+
{
125+
return $this->columnId;
126+
}
127+
128+
public function columnName(): string
129+
{
130+
return $this->columnName;
131+
}
132+
133+
public function nextRow(int $offset = 1): self
134+
{
135+
$newRowId = $this->rowId + $offset;
136+
if ($newRowId < 1) {
137+
$newRowId = 1;
138+
}
139+
140+
return static::fromColumnAndRow($this->columnId, $newRowId);
141+
}
142+
143+
public function previousRow(int $offset = 1): self
144+
{
145+
return $this->nextRow(0 - $offset);
146+
}
147+
148+
public function nextColumn(int $offset = 1): self
149+
{
150+
$newColumnId = $this->columnId + $offset;
151+
if ($newColumnId < 1) {
152+
$newColumnId = 1;
153+
}
154+
155+
return static::fromColumnAndRow($newColumnId, $this->rowId);
156+
}
157+
158+
public function previousColumn(int $offset = 1): self
159+
{
160+
return $this->nextColumn(0 - $offset);
161+
}
162+
163+
/**
164+
* The returned address string will contain the worksheet name as well, if available,
165+
* (ie. if a Worksheet was provided to the constructor).
166+
* e.g. "'Mark''s Worksheet'!C5".
167+
*/
168+
public function __toString()
169+
{
170+
return $this->fullCellAddress();
171+
}
172+
}

src/PhpSpreadsheet/Cell/CellRange.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Cell;
4+
5+
use PhpOffice\PhpSpreadsheet\Exception;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
7+
8+
class CellRange
9+
{
10+
/**
11+
* @var CellAddress
12+
*/
13+
protected $from;
14+
15+
/**
16+
* @var CellAddress
17+
*/
18+
protected $to;
19+
20+
public function __construct(CellAddress $from, CellAddress $to)
21+
{
22+
$this->validateFromTo($from, $to);
23+
}
24+
25+
private function validateFromTo(CellAddress $from, CellAddress $to): void
26+
{
27+
// Identify actual top-left and bottom-right values (in case we've been given top-right and bottom-left)
28+
$firstColumn = min($from->columnId(), $to->columnId());
29+
$firstRow = min($from->rowId(), $to->rowId());
30+
$lastColumn = max($from->columnId(), $to->columnId());
31+
$lastRow = max($from->rowId(), $to->rowId());
32+
33+
$fromWorksheet = $from->worksheet();
34+
$toWorksheet = $to->worksheet();
35+
$this->validateWorksheets($fromWorksheet, $toWorksheet);
36+
37+
$this->from = CellAddress::fromColumnAndRow($firstColumn, $firstRow, $fromWorksheet);
38+
$this->to = CellAddress::fromColumnAndRow($lastColumn, $lastRow, $toWorksheet);
39+
}
40+
41+
private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet): void
42+
{
43+
if ($fromWorksheet !== null && $toWorksheet !== null) {
44+
// We could simply compare worksheets rather than worksheet titles; but at some point we may introduce
45+
// support for 3d ranges; and at that point we drop this check and let the validation fall through
46+
// to the check for same workbook; but unless we check on titles, this test will also detect if the
47+
// worksheets are in different spreadsheets, and the next check will never execute or throw its
48+
// own exception.
49+
if ($fromWorksheet->getTitle() !== $toWorksheet->getTitle()) {
50+
throw new Exception('3d Cell Ranges are not supported');
51+
} elseif ($fromWorksheet->getParent() !== $toWorksheet->getParent()) {
52+
throw new Exception('Worksheets must be in the same spreadsheet');
53+
}
54+
}
55+
}
56+
57+
public function from(): CellAddress
58+
{
59+
return $this->from;
60+
}
61+
62+
public function to(): CellAddress
63+
{
64+
return $this->to;
65+
}
66+
67+
public function __toString(): string
68+
{
69+
if ($this->from->cellAddress() === $this->to->cellAddress()) {
70+
return "{$this->from->fullCellAddress()}";
71+
}
72+
73+
$fromAddress = $this->from->fullCellAddress();
74+
$toAddress = $this->to->cellAddress();
75+
76+
return "{$fromAddress}:{$toAddress}";
77+
}
78+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Cell;
4+
5+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
6+
7+
class ColumnRange
8+
{
9+
private const MAX_ROW = 1048576;
10+
11+
/**
12+
* @var ?Worksheet
13+
*/
14+
protected $worksheet;
15+
16+
/**
17+
* @var int
18+
*/
19+
protected $from;
20+
21+
/**
22+
* @var int
23+
*/
24+
protected $to;
25+
26+
public function __construct(string $from, ?string $to = null, ?Worksheet $worksheet = null)
27+
{
28+
$this->validateFromTo(
29+
Coordinate::columnIndexFromString($from),
30+
Coordinate::columnIndexFromString($to ?? $from)
31+
);
32+
$this->worksheet = $worksheet;
33+
}
34+
35+
public static function fromColumnIndexes(int $from, int $to, ?Worksheet $worksheet = null): self
36+
{
37+
return new self(Coordinate::stringFromColumnIndex($from), Coordinate::stringFromColumnIndex($to), $worksheet);
38+
}
39+
40+
/**
41+
* @param array<int|string> $array
42+
*/
43+
public static function fromArray(array $array, ?Worksheet $worksheet = null): self
44+
{
45+
array_walk(
46+
$array,
47+
function (&$column): void {
48+
$column = is_numeric($column) ? Coordinate::stringFromColumnIndex((int) $column) : $column;
49+
}
50+
);
51+
/** @var string $from */
52+
/** @var string $to */
53+
[$from, $to] = $array;
54+
55+
return new self($from, $to, $worksheet);
56+
}
57+
58+
private function validateFromTo(int $from, int $to): void
59+
{
60+
// Identify actual top and bottom values (in case we've been given bottom and top)
61+
$this->from = min($from, $to);
62+
$this->to = max($from, $to);
63+
}
64+
65+
public function columnCount(): int
66+
{
67+
return $this->to - $this->from + 1;
68+
}
69+
70+
public function from(): string
71+
{
72+
return Coordinate::stringFromColumnIndex($this->from);
73+
}
74+
75+
public function to(): string
76+
{
77+
return Coordinate::stringFromColumnIndex($this->to);
78+
}
79+
80+
public function fromIndex(): int
81+
{
82+
return $this->from;
83+
}
84+
85+
public function toIndex(): int
86+
{
87+
return $this->to;
88+
}
89+
90+
public function toCellRange(): CellRange
91+
{
92+
return new CellRange(
93+
CellAddress::fromColumnAndRow($this->from, 1, $this->worksheet),
94+
CellAddress::fromColumnAndRow($this->to, self::MAX_ROW)
95+
);
96+
}
97+
98+
public function __toString(): string
99+
{
100+
$from = $this->from();
101+
$to = $this->to();
102+
103+
if ($this->worksheet !== null) {
104+
$title = str_replace("'", "''", $this->worksheet->getTitle());
105+
106+
return "'{$title}'!{$from}:{$to}";
107+
}
108+
109+
return "{$from}:{$to}";
110+
}
111+
}

0 commit comments

Comments
 (0)