Completed
Push — develop ( 870d86...2ad559 )
by Adrien
42:24 queued 15:50
created

Cells::removeRow()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 9
loc 9
ccs 6
cts 6
cp 1
crap 3
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Collection;
4
5
use PhpOffice\PhpSpreadsheet\Cell;
6
use PhpOffice\PhpSpreadsheet\Worksheet;
7
use Psr\SimpleCache\CacheInterface;
8
9
/**
10
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
11
 *
12
 * This library is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU Lesser General Public
14
 * License as published by the Free Software Foundation; either
15
 * version 2.1 of the License, or (at your option) any later version.
16
 *
17
 * This library is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
 * Lesser General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Lesser General Public
23
 * License along with this library; if not, write to the Free Software
24
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25
 *
26
 * @category   PhpSpreadsheet
27
 *
28
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
29
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
30
 */
31
class Cells
32
{
33
    /**
34
     * @var \Psr\SimpleCache\CacheInterface
35
     */
36
    private $cache;
37
38
    /**
39
     * Parent worksheet.
40
     *
41
     * @var Worksheet
42
     */
43
    private $parent;
44
45
    /**
46
     * The currently active Cell.
47
     *
48
     * @var Cell
49
     */
50
    private $currentCell = null;
51
52
    /**
53
     * Coordinate of the currently active Cell.
54
     *
55
     * @var string
56
     */
57
    private $currentCoordinate = null;
58
59
    /**
60
     * Flag indicating whether the currently active Cell requires saving.
61
     *
62
     * @var bool
63
     */
64
    private $currentCellIsDirty = false;
65
66
    /**
67
     * An index of existing cells. Booleans indexed by their coordinate.
68
     *
69
     * @var bool[]
70
     */
71
    private $index = [];
72
73
    /**
74
     * Prefix used to uniquely identify cache data for this worksheet.
75
     *
76
     * @var string
77
     */
78
    private $cachePrefix = null;
79
80
    /**
81
     * Initialise this new cell collection.
82
     *
83
     * @param Worksheet $parent The worksheet for this cell collection
84
     */
85 86
    public function __construct(Worksheet $parent, CacheInterface $cache)
86
    {
87
        // Set our parent worksheet.
88
        // This is maintained here to facilitate re-attaching it to Cell objects when
89
        // they are woken from a serialized state
90 86
        $this->parent = $parent;
91 86
        $this->cache = $cache;
92 86
        $this->cachePrefix = $this->getUniqueID();
93 86
    }
94
95
    /**
96
     * Return the parent worksheet for this cell collection.
97
     *
98
     * @return Worksheet
99
     */
100 46
    public function getParent()
101
    {
102 46
        return $this->parent;
103
    }
104
105
    /**
106
     * Whether the collection holds a cell for the given coordinate.
107
     *
108
     * @param string $pCoord Coordinate of the cell to check
109
     *
110
     * @return bool
111
     */
112 76
    public function has($pCoord)
113
    {
114 76
        if ($pCoord === $this->currentCoordinate) {
115 68
            return true;
116
        }
117
118
        // Check if the requested entry exists in the index
119 76
        return isset($this->index[$pCoord]);
120
    }
121
122
    /**
123
     * Add or update a cell in the collection.
124
     *
125
     * @param Cell $cell Cell to update
126
     *
127
     * @throws \PhpOffice\PhpSpreadsheet\Exception
128
     *
129
     * @return Cell
130
     */
131 75
    public function update(Cell $cell)
132
    {
133 75
        return $this->add($cell->getCoordinate(), $cell);
134
    }
135
136
    /**
137
     * Delete a cell in cache identified by coordinate.
138
     *
139
     * @param string $pCoord Coordinate of the cell to delete
140
     *
141
     * @throws \PhpOffice\PhpSpreadsheet\Exception
142
     */
143 17
    public function delete($pCoord)
144
    {
145 17
        if ($pCoord === $this->currentCoordinate && !is_null($this->currentCell)) {
146
            $this->currentCell->detach();
147
            $this->currentCoordinate = null;
148
            $this->currentCell = null;
149
            $this->currentCellIsDirty = false;
150
        }
151
152 17
        unset($this->index[$pCoord]);
153
154
        // Delete the entry from cache
155 17
        $this->cache->delete($this->cachePrefix . $pCoord);
156 17
    }
157
158
    /**
159
     * Get a list of all cell coordinates currently held in the collection.
160
     *
161
     * @return string[]
162
     */
163 67
    public function getCoordinates()
164
    {
165 67
        return array_keys($this->index);
166
    }
167
168
    /**
169
     * Get a sorted list of all cell coordinates currently held in the collection by row and column.
170
     *
171
     * @return string[]
172
     */
173 63
    public function getSortedCoordinates()
174
    {
175 63
        $sortKeys = [];
176 63
        foreach ($this->getCoordinates() as $coord) {
177 63
            sscanf($coord, '%[A-Z]%d', $column, $row);
0 ignored issues
show
Bug introduced by
The variable $row does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
178 63
            $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord;
179
        }
180 63
        ksort($sortKeys);
181
182 63
        return array_values($sortKeys);
183
    }
184
185
    /**
186
     * Get highest worksheet column and highest row that have cell records.
187
     *
188
     * @return array Highest column name and highest row number
189
     */
190 62
    public function getHighestRowAndColumn()
191
    {
192
        // Lookup highest column and highest row
193 62
        $col = ['A' => '1A'];
194 62
        $row = [1];
195 62
        foreach ($this->getCoordinates() as $coord) {
196 62
            sscanf($coord, '%[A-Z]%d', $c, $r);
0 ignored issues
show
Bug introduced by
The variable $r does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
197 62
            $row[$r] = $r;
198 62
            $col[$c] = strlen($c) . $c;
199
        }
200 62
        if (!empty($row)) {
201
            // Determine highest column and row
202 62
            $highestRow = max($row);
203 62
            $highestColumn = substr(max($col), 1);
204
        }
205
206
        return [
207 62
            'row' => $highestRow,
0 ignored issues
show
Bug introduced by
The variable $highestRow does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
208 62
            'column' => $highestColumn,
0 ignored issues
show
Bug introduced by
The variable $highestColumn does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
209
        ];
210
    }
211
212
    /**
213
     * Return the cell coordinate of the currently active cell object.
214
     *
215
     * @return string
216
     */
217 76
    public function getCurrentCoordinate()
218
    {
219 76
        return $this->currentCoordinate;
220
    }
221
222
    /**
223
     * Return the column coordinate of the currently active cell object.
224
     *
225
     * @return string
226
     */
227 47
    public function getCurrentColumn()
228
    {
229 47
        sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row);
0 ignored issues
show
Bug introduced by
The variable $row does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
230
231 47
        return $column;
232
    }
233
234
    /**
235
     * Return the row coordinate of the currently active cell object.
236
     *
237
     * @return int
238
     */
239 45
    public function getCurrentRow()
240
    {
241 45
        sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row);
0 ignored issues
show
Bug introduced by
The variable $row does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
242
243 45
        return (int) $row;
244
    }
245
246
    /**
247
     * Get highest worksheet column.
248
     *
249
     * @param string $row Return the highest column for the specified row,
250
     *                    or the highest column of any row if no row number is passed
251
     *
252
     * @return string Highest column name
253
     */
254 14
    public function getHighestColumn($row = null)
255
    {
256 14
        if ($row == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $row of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
257 14
            $colRow = $this->getHighestRowAndColumn();
258
259 14
            return $colRow['column'];
260
        }
261
262
        $columnList = [1];
263
        foreach ($this->getCoordinates() as $coord) {
264
            sscanf($coord, '%[A-Z]%d', $c, $r);
0 ignored issues
show
Bug introduced by
The variable $r does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
265
            if ($r != $row) {
266
                continue;
267
            }
268
            $columnList[] = Cell::columnIndexFromString($c);
269
        }
270
271
        return Cell::stringFromColumnIndex(max($columnList) - 1);
272
    }
273
274
    /**
275
     * Get highest worksheet row.
276
     *
277
     * @param string $column Return the highest row for the specified column,
278
     *                       or the highest row of any column if no column letter is passed
279
     *
280
     * @return int Highest row number
281
     */
282 16
    public function getHighestRow($column = null)
283
    {
284 16
        if ($column == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $column of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
285 16
            $colRow = $this->getHighestRowAndColumn();
286
287 16
            return $colRow['row'];
288
        }
289
290
        $rowList = [0];
291
        foreach ($this->getCoordinates() as $coord) {
292
            sscanf($coord, '%[A-Z]%d', $c, $r);
0 ignored issues
show
Bug introduced by
The variable $r does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
293
            if ($c != $column) {
294
                continue;
295
            }
296
            $rowList[] = $r;
297
        }
298
299
        return max($rowList);
300
    }
301
302
    /**
303
     * Generate a unique ID for cache referencing.
304
     *
305
     * @return string Unique Reference
306
     */
307 86
    private function getUniqueID()
308
    {
309 86
        return uniqid('phpspreadsheet-', true) . '-';
310
    }
311
312
    /**
313
     * Clone the cell collection.
314
     *
315
     * @param Worksheet $parent The new worksheet that we're copying to
316
     *
317
     * @return self
318
     */
319 2
    public function cloneCellCollection(Worksheet $parent)
320
    {
321 2
        $this->storeCurrentCell();
322 2
        $newCollection = clone $this;
323
324 2
        $newCollection->parent = $parent;
325 2
        if (($newCollection->currentCell !== null) && (is_object($newCollection->currentCell))) {
326
            $newCollection->currentCell->attach($this);
327
        }
328
329
        // Get old values
330 2
        $oldKeys = $newCollection->getAllCacheKeys();
331 2
        $oldValues = $newCollection->cache->getMultiple($oldKeys);
0 ignored issues
show
Documentation introduced by
$oldKeys is of type array, but the function expects a object<Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
332 2
        $newValues = [];
333 2
        $oldCachePrefix = $newCollection->cachePrefix;
334
335
        // Change prefix
336 2
        $newCollection->cachePrefix = $newCollection->getUniqueID();
337 2
        foreach ($oldValues as $oldKey => $value) {
338 2
            $newValues[str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey)] = clone $value;
339
        }
340
341
        // Store new values
342 2
        $stored = $newCollection->cache->setMultiple($newValues);
0 ignored issues
show
Documentation introduced by
$newValues is of type array, but the function expects a object<Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
343 2
        if (!$stored) {
344
            $newCollection->__destruct();
345
            throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to copy cells in cache');
346
        }
347
348 2
        return $newCollection;
349
    }
350
351
    /**
352
     * Remove a row, deleting all cells in that row.
353
     *
354
     * @param string $row Row number to remove
355
     */
356 15 View Code Duplication
    public function removeRow($row)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
357
    {
358 15
        foreach ($this->getCoordinates() as $coord) {
359 15
            sscanf($coord, '%[A-Z]%d', $c, $r);
0 ignored issues
show
Bug introduced by
The variable $r does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
360 15
            if ($r == $row) {
361 15
                $this->delete($coord);
362
            }
363
        }
364 15
    }
365
366
    /**
367
     * Remove a column, deleting all cells in that column.
368
     *
369
     * @param string $column Column ID to remove
370
     */
371 12 View Code Duplication
    public function removeColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373 12
        foreach ($this->getCoordinates() as $coord) {
374 12
            sscanf($coord, '%[A-Z]%d', $c, $r);
0 ignored issues
show
Bug introduced by
The variable $r does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
375 12
            if ($c == $column) {
376 12
                $this->delete($coord);
377
            }
378
        }
379 12
    }
380
381
    /**
382
     * Store cell data in cache for the current cell object if it's "dirty",
383
     * and the 'nullify' the current cell object.
384
     *
385
     * @throws \PhpOffice\PhpSpreadsheet\Exception
386
     */
387 78
    private function storeCurrentCell()
388
    {
389 78
        if ($this->currentCellIsDirty && !empty($this->currentCoordinate)) {
390 74
            $this->currentCell->detach();
391
392 74
            $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell);
393 74
            if (!$stored) {
394 1
                $this->__destruct();
395 1
                throw new \PhpOffice\PhpSpreadsheet\Exception("Failed to store cell {$this->currentCoordinate} in cache");
396
            }
397 73
            $this->currentCellIsDirty = false;
398
        }
399
400 78
        $this->currentCoordinate = null;
401 78
        $this->currentCell = null;
402 78
    }
403
404
    /**
405
     * Add or update a cell identified by its coordinate into the collection.
406
     *
407
     * @param string $pCoord Coordinate of the cell to update
408
     * @param Cell $cell Cell to update
409
     *
410
     * @throws \PhpOffice\PhpSpreadsheet\Exception
411
     *
412
     * @return Cell
413
     */
414 77
    public function add($pCoord, Cell $cell)
415
    {
416 77
        if ($pCoord !== $this->currentCoordinate) {
417 77
            $this->storeCurrentCell();
418
        }
419 77
        $this->index[$pCoord] = true;
420
421 77
        $this->currentCoordinate = $pCoord;
422 77
        $this->currentCell = $cell;
423 77
        $this->currentCellIsDirty = true;
424
425 77
        return $cell;
426
    }
427
428
    /**
429
     * Get cell at a specific coordinate.
430
     *
431
     * @param string $pCoord Coordinate of the cell
432
     *
433
     * @throws \PhpOffice\PhpSpreadsheet\Exception
434
     *
435
     * @return Cell Cell that was found, or null if not found
436
     */
437 75
    public function get($pCoord)
438
    {
439 75
        if ($pCoord === $this->currentCoordinate) {
440 68
            return $this->currentCell;
441
        }
442 71
        $this->storeCurrentCell();
443
444
        // Return null if requested entry doesn't exist in collection
445 71
        if (!$this->has($pCoord)) {
446 21
            return null;
447
        }
448
449
        // Check if the entry that has been requested actually exists
450 71
        $cell = $this->cache->get($this->cachePrefix . $pCoord);
451 71
        if ($cell === null) {
452 1
            throw new \PhpOffice\PhpSpreadsheet\Exception("Cell entry {$pCoord} no longer exists in cache. This probably means that the cache was cleared by someone else.");
453
        }
454
455
        // Set current entry to the requested entry
456 70
        $this->currentCoordinate = $pCoord;
457 70
        $this->currentCell = $cell;
458
        // Re-attach this as the cell's parent
459 70
        $this->currentCell->attach($this);
460
461
        // Return requested entry
462 70
        return $this->currentCell;
463
    }
464
465
    /**
466
     * Clear the cell collection and disconnect from our parent.
467
     */
468 1
    public function unsetWorksheetCells()
469
    {
470 1
        if (!is_null($this->currentCell)) {
471 1
            $this->currentCell->detach();
472 1
            $this->currentCell = null;
473 1
            $this->currentCoordinate = null;
474
        }
475
476
        // Flush the cache
477 1
        $this->__destruct();
478
479 1
        $this->index = [];
480
481
        // detach ourself from the worksheet, so that it can then delete this object successfully
482 1
        $this->parent = null;
483 1
    }
484
485
    /**
486
     * Destroy this cell collection.
487
     */
488 3
    public function __destruct()
489
    {
490 3
        $this->cache->deleteMultiple($this->getAllCacheKeys());
0 ignored issues
show
Documentation introduced by
$this->getAllCacheKeys() is of type array, but the function expects a object<Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
491 3
    }
492
493
    /**
494
     * Returns all known cache keys.
495
     *
496
     * @return string
497
     */
498 5
    private function getAllCacheKeys()
499
    {
500 5
        $keys = [];
501 5
        foreach ($this->getCoordinates() as $coordinate) {
502 4
            $keys[] = $this->cachePrefix . $coordinate;
503
        }
504
505 5
        return $keys;
506
    }
507
}
508