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

Cell::setValueExplicit()   B

Complexity

Conditions 11
Paths 4

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 11.044

Importance

Changes 0
Metric Value
cc 11
eloc 29
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 47
ccs 26
cts 28
cp 0.9286
crap 11.044
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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