Failed Conditions
Push — master ( 0c93bb...a2be57 )
by Adrien
29:37 queued 19:11
created

Cell::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.5923

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 3
dl 0
loc 16
ccs 6
cts 9
cp 0.6667
crap 4.5923
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Cell;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Collection\Cells;
7
use PhpOffice\PhpSpreadsheet\Exception;
8
use PhpOffice\PhpSpreadsheet\RichText\RichText;
9
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
10
use PhpOffice\PhpSpreadsheet\Style\Style;
11
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
12
use Throwable;
13
14
class Cell
15
{
16
    /**
17
     * Value binder to use.
18
     *
19
     * @var IValueBinder
20
     */
21
    private static $valueBinder;
22
23
    /**
24
     * Value of the cell.
25
     *
26
     * @var mixed
27
     */
28
    private $value;
29
30
    /**
31
     *    Calculated value of the cell (used for caching)
32
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
33
     *        create the original spreadsheet file.
34
     *    Note that this value is not guaranteed to reflect the actual calculated value because it is
35
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
36
     *        values used by the formula have changed since it was last calculated.
37
     *
38
     * @var mixed
39
     */
40
    private $calculatedValue;
41
42
    /**
43
     * Type of the cell data.
44
     *
45
     * @var string
46
     */
47
    private $dataType;
48
49
    /**
50
     * Collection of cells.
51
     *
52
     * @var Cells
53
     */
54
    private $parent;
55
56
    /**
57
     * Index to cellXf.
58
     *
59
     * @var int
60
     */
61
    private $xfIndex = 0;
62
63
    /**
64
     * Attributes of the formula.
65
     */
66
    private $formulaAttributes;
67
68
    /**
69
     * Update the cell into the cell collection.
70
     *
71
     * @return $this
72
     */
73 4802
    public function updateInCollection(): self
74
    {
75 4802
        $this->parent->update($this);
76
77 4802
        return $this;
78
    }
79
80 4583
    public function detach(): void
81
    {
82
        // @phpstan-ignore-next-line
83 4583
        $this->parent = null;
84 4583
    }
85
86 4199
    public function attach(Cells $parent): void
87
    {
88 4199
        $this->parent = $parent;
89 4199
    }
90
91
    /**
92
     * Create a new Cell.
93
     *
94
     * @param mixed $value
95
     * @param string $dataType
96
     */
97 4820
    public function __construct($value, $dataType, Worksheet $worksheet)
98
    {
99
        // Initialise cell value
100 4820
        $this->value = $value;
101
102
        // Set worksheet cache
103 4820
        $this->parent = $worksheet->getCellCollection();
104
105
        // Set datatype?
106 4820
        if ($dataType !== null) {
0 ignored issues
show
introduced by
The condition $dataType !== null is always true.
Loading history...
107 4820
            if ($dataType == DataType::TYPE_STRING2) {
108
                $dataType = DataType::TYPE_STRING;
109
            }
110 4820
            $this->dataType = $dataType;
111
        } elseif (!self::getValueBinder()->bindValue($this, $value)) {
112
            throw new Exception('Value could not be bound to cell.');
113
        }
114 4820
    }
115
116
    /**
117
     * Get cell coordinate column.
118
     *
119
     * @return string
120
     */
121 197
    public function getColumn()
122
    {
123 197
        return $this->parent->getCurrentColumn();
124
    }
125
126
    /**
127
     * Get cell coordinate row.
128
     *
129
     * @return int
130
     */
131 178
    public function getRow()
132
    {
133 178
        return $this->parent->getCurrentRow();
134
    }
135
136
    /**
137
     * Get cell coordinate.
138
     *
139
     * @return string
140
     */
141 4809
    public function getCoordinate()
142
    {
143
        try {
144 4809
            $coordinate = $this->parent->getCurrentCoordinate();
145 2
        } catch (Throwable $e) {
146 2
            $coordinate = null;
147
        }
148 4809
        if ($coordinate === null) {
149 2
            throw new Exception('Coordinate no longer exists');
150
        }
151
152 4809
        return $coordinate;
153
    }
154
155
    /**
156
     * Get cell value.
157
     *
158
     * @return mixed
159
     */
160 4577
    public function getValue()
161
    {
162 4577
        return $this->value;
163
    }
164
165
    /**
166
     * Get cell value with formatting.
167
     *
168
     * @return string
169
     */
170 45
    public function getFormattedValue()
171
    {
172 45
        return (string) NumberFormat::toFormattedString(
173 45
            $this->getCalculatedValue(),
174 45
            $this->getStyle()
175 45
                ->getNumberFormat()->getFormatCode()
176
        );
177
    }
178
179
    /**
180
     * Set cell value.
181
     *
182
     *    Sets the value for a cell, automatically determining the datatype using the value binder
183
     *
184
     * @param mixed $value Value
185
     *
186
     * @return $this
187
     */
188 4681
    public function setValue($value)
189
    {
190 4681
        if (!self::getValueBinder()->bindValue($this, $value)) {
191
            throw new Exception('Value could not be bound to cell.');
192
        }
193
194 4681
        return $this;
195
    }
196
197
    /**
198
     * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
199
     *
200
     * @param mixed $value Value
201
     * @param string $dataType Explicit data type, see DataType::TYPE_*
202
     *
203
     * @return Cell
204
     */
205 4781
    public function setValueExplicit($value, $dataType)
206
    {
207
        // set the value according to data type
208 4635
        switch ($dataType) {
209 147
            case DataType::TYPE_NULL:
210 85
                $this->value = $value;
211
212 85
                break;
213 146
            case DataType::TYPE_STRING2:
214 2
                $dataType = DataType::TYPE_STRING;
215
                // no break
216 146
            case DataType::TYPE_STRING:
217
                // Synonym for string
218 130
            case DataType::TYPE_INLINE:
219
                // Rich text
220 2292
                $this->value = DataType::checkString($value);
221
222 2292
                break;
223 129
            case DataType::TYPE_NUMERIC:
224 3002
                if (is_string($value) && !is_numeric($value)) {
225 1
                    throw new Exception('Invalid numeric value for datatype Numeric');
226
                }
227 3001
                $this->value = 0 + $value;
228
229 3001
                break;
230 103
            case DataType::TYPE_FORMULA:
231 4082
                $this->value = (string) $value;
232
233 4082
                break;
234 17
            case DataType::TYPE_BOOL:
235 225
                $this->value = (bool) $value;
236
237 225
                break;
238
            case DataType::TYPE_ERROR:
239 12
                $this->value = DataType::checkErrorCode($value);
240
241 12
                break;
242
            default:
243
                throw new Exception('Invalid datatype: ' . $dataType);
244
245
                break;
246
        }
247
248
        // set the datatype
249 4781
        $this->dataType = $dataType;
250
251 4781
        return $this->updateInCollection();
252
    }
253
254
    /**
255
     * Get calculated cell value.
256
     *
257
     * @param bool $resetLog Whether the calculation engine logger should be reset or not
258
     *
259
     * @return mixed
260
     */
261 4304
    public function getCalculatedValue($resetLog = true)
262
    {
263 4304
        if ($this->dataType == DataType::TYPE_FORMULA) {
264
            try {
265 4012
                $index = $this->getWorksheet()->getParent()->getActiveSheetIndex();
266 4012
                $selected = $this->getWorksheet()->getSelectedCells();
267 4012
                $result = Calculation::getInstance(
268 4012
                    $this->getWorksheet()->getParent()
269 4012
                )->calculateCellValue($this, $resetLog);
270 3840
                $this->getWorksheet()->setSelectedCells($selected);
271 3840
                $this->getWorksheet()->getParent()->setActiveSheetIndex($index);
272
                //    We don't yet handle array returns
273 3840
                if (is_array($result)) {
274 3840
                    while (is_array($result)) {
275 44
                        $result = array_shift($result);
276
                    }
277
                }
278 182
            } catch (Exception $ex) {
279 182
                if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
280 1
                    return $this->calculatedValue; // Fallback for calculations referencing external files.
281 182
                } elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) {
282 26
                    return \PhpOffice\PhpSpreadsheet\Calculation\Functions::NAME();
283
                }
284
285 156
                throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
286 156
                    $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
287
                );
288
            }
289
290 3840
            if ($result === '#Not Yet Implemented') {
291 2
                return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
292
            }
293
294 3840
            return $result;
295 1848
        } elseif ($this->value instanceof RichText) {
296 3
            return $this->value->getPlainText();
297
        }
298
299 1847
        return $this->value;
300
    }
301
302
    /**
303
     * Set old calculated value (cached).
304
     *
305
     * @param mixed $originalValue Value
306
     *
307
     * @return Cell
308
     */
309 116
    public function setCalculatedValue($originalValue)
310
    {
311 116
        if ($originalValue !== null) {
312 116
            $this->calculatedValue = (is_numeric($originalValue)) ? (float) $originalValue : $originalValue;
313
        }
314
315 116
        return $this->updateInCollection();
316
    }
317
318
    /**
319
     *    Get old calculated value (cached)
320
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
321
     *        create the original spreadsheet file.
322
     *    Note that this value is not guaranteed to reflect the actual calculated value because it is
323
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
324
     *        values used by the formula have changed since it was last calculated.
325
     *
326
     * @return mixed
327
     */
328 3
    public function getOldCalculatedValue()
329
    {
330 3
        return $this->calculatedValue;
331
    }
332
333
    /**
334
     * Get cell data type.
335
     *
336
     * @return string
337
     */
338 516
    public function getDataType()
339
    {
340 516
        return $this->dataType;
341
    }
342
343
    /**
344
     * Set cell data type.
345
     *
346
     * @param string $dataType see DataType::TYPE_*
347
     *
348
     * @return Cell
349
     */
350
    public function setDataType($dataType)
351
    {
352
        if ($dataType == DataType::TYPE_STRING2) {
353
            $dataType = DataType::TYPE_STRING;
354
        }
355
        $this->dataType = $dataType;
356
357
        return $this->updateInCollection();
358
    }
359
360
    /**
361
     * Identify if the cell contains a formula.
362
     *
363
     * @return bool
364
     */
365 42
    public function isFormula()
366
    {
367 42
        return $this->dataType == DataType::TYPE_FORMULA;
368
    }
369
370
    /**
371
     *    Does this cell contain Data validation rules?
372
     *
373
     * @return bool
374
     */
375 6
    public function hasDataValidation()
376
    {
377 6
        if (!isset($this->parent)) {
378
            throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
379
        }
380
381 6
        return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
382
    }
383
384
    /**
385
     * Get Data validation rules.
386
     *
387
     * @return DataValidation
388
     */
389 9
    public function getDataValidation()
390
    {
391 9
        if (!isset($this->parent)) {
392
            throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
393
        }
394
395 9
        return $this->getWorksheet()->getDataValidation($this->getCoordinate());
396
    }
397
398
    /**
399
     * Set Data validation rules.
400
     */
401
    public function setDataValidation(?DataValidation $dataValidation = null): self
402
    {
403
        if (!isset($this->parent)) {
404
            throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
405
        }
406
407
        $this->getWorksheet()->setDataValidation($this->getCoordinate(), $dataValidation);
408
409
        return $this->updateInCollection();
410
    }
411
412
    /**
413
     * Does this cell contain valid value?
414
     *
415
     * @return bool
416
     */
417 4
    public function hasValidValue()
418
    {
419 4
        $validator = new DataValidator();
420
421 4
        return $validator->isValid($this);
422
    }
423
424
    /**
425
     * Does this cell contain a Hyperlink?
426
     *
427
     * @return bool
428
     */
429
    public function hasHyperlink()
430
    {
431
        if (!isset($this->parent)) {
432
            throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
433
        }
434
435
        return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
436
    }
437
438
    /**
439
     * Get Hyperlink.
440
     *
441
     * @return Hyperlink
442
     */
443 43
    public function getHyperlink()
444
    {
445 43
        if (!isset($this->parent)) {
446
            throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
447
        }
448
449 43
        return $this->getWorksheet()->getHyperlink($this->getCoordinate());
450
    }
451
452
    /**
453
     * Set Hyperlink.
454
     *
455
     * @return Cell
456
     */
457
    public function setHyperlink(?Hyperlink $hyperlink = null)
458
    {
459
        if (!isset($this->parent)) {
460
            throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
461
        }
462
463
        $this->getWorksheet()->setHyperlink($this->getCoordinate(), $hyperlink);
464
465
        return $this->updateInCollection();
466
    }
467
468
    /**
469
     * Get cell collection.
470
     *
471
     * @return Cells
472
     */
473 3877
    public function getParent()
474
    {
475 3877
        return $this->parent;
476
    }
477
478
    /**
479
     * Get parent worksheet.
480
     *
481
     * @return Worksheet
482
     */
483 4273
    public function getWorksheet()
484
    {
485
        try {
486 4273
            $worksheet = $this->parent->getParent();
487 1
        } catch (Throwable $e) {
488 1
            $worksheet = null;
489
        }
490
491 4273
        if ($worksheet === null) {
492 1
            throw new Exception('Worksheet no longer exists');
493
        }
494
495 4273
        return $worksheet;
496
    }
497
498
    /**
499
     * Is this cell in a merge range.
500
     *
501
     * @return bool
502
     */
503
    public function isInMergeRange()
504
    {
505
        return (bool) $this->getMergeRange();
506
    }
507
508
    /**
509
     * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
510
     *
511
     * @return bool
512
     */
513 21
    public function isMergeRangeValueCell()
514
    {
515 21
        if ($mergeRange = $this->getMergeRange()) {
516 1
            $mergeRange = Coordinate::splitRange($mergeRange);
517 1
            [$startCell] = $mergeRange[0];
518 1
            if ($this->getCoordinate() === $startCell) {
519 1
                return true;
520
            }
521
        }
522
523 21
        return false;
524
    }
525
526
    /**
527
     * If this cell is in a merge range, then return the range.
528
     *
529
     * @return false|string
530
     */
531 21
    public function getMergeRange()
532
    {
533 21
        foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
534 1
            if ($this->isInRange($mergeRange)) {
535 1
                return $mergeRange;
536
            }
537
        }
538
539 21
        return false;
540
    }
541
542
    /**
543
     * Get cell style.
544
     *
545
     * @return Style
546
     */
547 4206
    public function getStyle()
548
    {
549 4206
        return $this->getWorksheet()->getStyle($this->getCoordinate());
550
    }
551
552
    /**
553
     * Re-bind parent.
554
     *
555
     * @return Cell
556
     */
557
    public function rebindParent(Worksheet $parent)
558
    {
559
        $this->parent = $parent->getCellCollection();
560
561
        return $this->updateInCollection();
562
    }
563
564
    /**
565
     *    Is cell in a specific range?
566
     *
567
     * @param string $range Cell range (e.g. A1:A1)
568
     *
569
     * @return bool
570
     */
571 1
    public function isInRange($range)
572
    {
573 1
        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range);
574
575
        // Translate properties
576 1
        $myColumn = Coordinate::columnIndexFromString($this->getColumn());
577 1
        $myRow = $this->getRow();
578
579
        // Verify if cell is in range
580 1
        return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
581 1
                ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
582
    }
583
584
    /**
585
     * Compare 2 cells.
586
     *
587
     * @param Cell $a Cell a
588
     * @param Cell $b Cell b
589
     *
590
     * @return int Result of comparison (always -1 or 1, never zero!)
591
     */
592
    public static function compareCells(self $a, self $b)
593
    {
594
        if ($a->getRow() < $b->getRow()) {
595
            return -1;
596
        } elseif ($a->getRow() > $b->getRow()) {
597
            return 1;
598
        } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) {
599
            return -1;
600
        }
601
602
        return 1;
603
    }
604
605
    /**
606
     * Get value binder to use.
607
     *
608
     * @return IValueBinder
609
     */
610 4682
    public static function getValueBinder()
611
    {
612 4682
        if (self::$valueBinder === null) {
613 125
            self::$valueBinder = new DefaultValueBinder();
614
        }
615
616 4682
        return self::$valueBinder;
617
    }
618
619
    /**
620
     * Set value binder to use.
621
     */
622 103
    public static function setValueBinder(IValueBinder $binder): void
623
    {
624 103
        self::$valueBinder = $binder;
625 103
    }
626
627
    /**
628
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
629
     */
630 6
    public function __clone()
631
    {
632 6
        $vars = get_object_vars($this);
633 6
        foreach ($vars as $key => $value) {
634 6
            if ((is_object($value)) && ($key != 'parent')) {
635
                $this->$key = clone $value;
636
            } else {
637 6
                $this->$key = $value;
638
            }
639
        }
640 6
    }
641
642
    /**
643
     * Get index to cellXf.
644
     *
645
     * @return int
646
     */
647 4475
    public function getXfIndex()
648
    {
649 4475
        return $this->xfIndex;
650
    }
651
652
    /**
653
     * Set index to cellXf.
654
     *
655
     * @param int $indexValue
656
     *
657
     * @return Cell
658
     */
659 596
    public function setXfIndex($indexValue)
660
    {
661 596
        $this->xfIndex = $indexValue;
662
663 596
        return $this->updateInCollection();
664
    }
665
666
    /**
667
     * Set the formula attributes.
668
     *
669
     * @param mixed $attributes
670
     *
671
     * @return $this
672
     */
673
    public function setFormulaAttributes($attributes)
674
    {
675
        $this->formulaAttributes = $attributes;
676
677
        return $this;
678
    }
679
680
    /**
681
     * Get the formula attributes.
682
     */
683 43
    public function getFormulaAttributes()
684
    {
685 43
        return $this->formulaAttributes;
686
    }
687
688
    /**
689
     * Convert to string.
690
     *
691
     * @return string
692
     */
693
    public function __toString()
694
    {
695
        return (string) $this->getValue();
696
    }
697
}
698