Passed
Push — master ( c380b2...9239b3 )
by Adrien
10:06
created

Cell::getDataType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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