Completed
Push — develop ( 1cec98...8d7602 )
by Adrien
24:39
created

Cell::setCalculatedValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 8
ccs 4
cts 4
cp 1
crap 3
rs 9.4285
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 self
71
     */
72 121
    public function updateInCollection()
73
    {
74 121
        $this->parent->update($this);
75
76 121
        return $this;
77
    }
78
79 111
    public function detach()
80
    {
81 111
        $this->parent = null;
82 111
    }
83
84 103
    public function attach(Cells $parent)
85
    {
86 103
        $this->parent = $parent;
87 103
    }
88
89
    /**
90
     * Create a new Cell.
91
     *
92
     * @param mixed $pValue
93
     * @param string $pDataType
94
     * @param Worksheet $pSheet
95
     *
96
     * @throws Exception
97
     */
98 125
    public function __construct($pValue, $pDataType, Worksheet $pSheet)
99
    {
100
        // Initialise cell value
101 125
        $this->value = $pValue;
102
103
        // Set worksheet cache
104 125
        $this->parent = $pSheet->getCellCollection();
105
106
        // Set datatype?
107 125
        if ($pDataType !== null) {
108 125
            if ($pDataType == DataType::TYPE_STRING2) {
109
                $pDataType = DataType::TYPE_STRING;
110
            }
111 125
            $this->dataType = $pDataType;
112
        } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
113
            throw new Exception('Value could not be bound to cell.');
114
        }
115 125
    }
116
117
    /**
118
     * Get cell coordinate column.
119
     *
120
     * @return string
121
     */
122 48
    public function getColumn()
123
    {
124 48
        return $this->parent->getCurrentColumn();
125
    }
126
127
    /**
128
     * Get cell coordinate row.
129
     *
130
     * @return int
131
     */
132 46
    public function getRow()
133
    {
134 46
        return $this->parent->getCurrentRow();
135
    }
136
137
    /**
138
     * Get cell coordinate.
139
     *
140
     * @return string
141
     */
142 125
    public function getCoordinate()
143
    {
144 125
        return $this->parent->getCurrentCoordinate();
145
    }
146
147
    /**
148
     * Get cell value.
149
     *
150
     * @return mixed
151
     */
152 109
    public function getValue()
153
    {
154 109
        return $this->value;
155
    }
156
157
    /**
158
     * Get cell value with formatting.
159
     *
160
     * @return string
161
     */
162 5
    public function getFormattedValue()
163
    {
164 5
        return (string) NumberFormat::toFormattedString(
165 5
            $this->getCalculatedValue(),
166 5
            $this->getStyle()
167 5
                ->getNumberFormat()->getFormatCode()
168
        );
169
    }
170
171
    /**
172
     * Set cell value.
173
     *
174
     *    Sets the value for a cell, automatically determining the datatype using the value binder
175
     *
176
     * @param mixed $pValue Value
177
     *
178
     * @throws Exception
179
     *
180
     * @return Cell
181
     */
182 79
    public function setValue($pValue)
183
    {
184 79
        if (!self::getValueBinder()->bindValue($this, $pValue)) {
185
            throw new Exception('Value could not be bound to cell.');
186
        }
187
188 79
        return $this;
189
    }
190
191
    /**
192
     * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
193
     *
194
     * @param mixed $pValue Value
195
     * @param string $pDataType Explicit data type, see DataType::TYPE_*
196
     *
197
     * @throws Exception
198
     *
199
     * @return Cell
200
     */
201 117
    public function setValueExplicit($pValue, $pDataType)
202
    {
203
        // set the value according to data type
204
        switch ($pDataType) {
205 117
            case DataType::TYPE_NULL:
206 29
                $this->value = $pValue;
207
208 29
                break;
209 117
            case DataType::TYPE_STRING2:
210
                $pDataType = DataType::TYPE_STRING;
211
                // no break
212 117
            case DataType::TYPE_STRING:
213
                // Synonym for string
214 100
            case DataType::TYPE_INLINE:
215
                // Rich text
216 101
                $this->value = DataType::checkString($pValue);
217
218 101
                break;
219 99
            case DataType::TYPE_NUMERIC:
220 93
                $this->value = (float) $pValue;
221
222 93
                break;
223 59
            case DataType::TYPE_FORMULA:
224 57
                $this->value = (string) $pValue;
225
226 57
                break;
227 25
            case DataType::TYPE_BOOL:
228 25
                $this->value = (bool) $pValue;
229
230 25
                break;
231
            case DataType::TYPE_ERROR:
232
                $this->value = DataType::checkErrorCode($pValue);
233
234
                break;
235
            default:
236
                throw new Exception('Invalid datatype: ' . $pDataType);
237
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
238
        }
239
240
        // set the datatype
241 117
        $this->dataType = $pDataType;
242
243 117
        return $this->updateInCollection();
244
    }
245
246
    /**
247
     * Get calculated cell value.
248
     *
249
     * @param bool $resetLog Whether the calculation engine logger should be reset or not
250
     *
251
     * @throws Exception
252
     *
253
     * @return mixed
254
     */
255 71
    public function getCalculatedValue($resetLog = true)
256
    {
257 71
        if ($this->dataType == DataType::TYPE_FORMULA) {
258
            try {
259 48
                $result = Calculation::getInstance(
260 48
                    $this->getWorksheet()->getParent()
261 48
                )->calculateCellValue($this, $resetLog);
262
                //    We don't yet handle array returns
263 48
                if (is_array($result)) {
264 48
                    while (is_array($result)) {
265 10
                        $result = array_pop($result);
266
                    }
267
                }
268
            } catch (Exception $ex) {
269
                if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
270
                    return $this->calculatedValue; // Fallback for calculations referencing external files.
271
                }
272
273
                throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
274
                    $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
275
                );
276
            }
277
278 48
            if ($result === '#Not Yet Implemented') {
279
                return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
280
            }
281
282 48
            return $result;
283 71
        } elseif ($this->value instanceof RichText) {
284 2
            return $this->value->getPlainText();
285
        }
286
287 71
        return $this->value;
288
    }
289
290
    /**
291
     * Set old calculated value (cached).
292
     *
293
     * @param mixed $pValue Value
294
     *
295
     * @return Cell
296
     */
297 22
    public function setCalculatedValue($pValue)
298
    {
299 22
        if ($pValue !== null) {
300 22
            $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
301
        }
302
303 22
        return $this->updateInCollection();
304
    }
305
306
    /**
307
     *    Get old calculated value (cached)
308
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
309
     *        create the original spreadsheet file.
310
     *    Note that this value is not guaranteed to refelect the actual calculated value because it is
311
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
312
     *        values used by the formula have changed since it was last calculated.
313
     *
314
     * @return mixed
315
     */
316
    public function getOldCalculatedValue()
317
    {
318
        return $this->calculatedValue;
319
    }
320
321
    /**
322
     * Get cell data type.
323
     *
324
     * @return string
325
     */
326 70
    public function getDataType()
327
    {
328 70
        return $this->dataType;
329
    }
330
331
    /**
332
     * Set cell data type.
333
     *
334
     * @param string $pDataType see DataType::TYPE_*
335
     *
336
     * @return Cell
337
     */
338
    public function setDataType($pDataType)
339
    {
340
        if ($pDataType == DataType::TYPE_STRING2) {
341
            $pDataType = DataType::TYPE_STRING;
342
        }
343
        $this->dataType = $pDataType;
344
345
        return $this->updateInCollection();
346
    }
347
348
    /**
349
     * Identify if the cell contains a formula.
350
     *
351
     * @return bool
352
     */
353
    public function isFormula()
354
    {
355
        return $this->dataType == DataType::TYPE_FORMULA;
356
    }
357
358
    /**
359
     *    Does this cell contain Data validation rules?
360
     *
361
     * @throws Exception
362
     *
363
     * @return bool
364
     */
365 3
    public function hasDataValidation()
366
    {
367 3
        if (!isset($this->parent)) {
368
            throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
369
        }
370
371 3
        return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
372
    }
373
374
    /**
375
     * Get Data validation rules.
376
     *
377
     * @throws Exception
378
     *
379
     * @return DataValidation
380
     */
381 4
    public function getDataValidation()
382
    {
383 4
        if (!isset($this->parent)) {
384
            throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
385
        }
386
387 4
        return $this->getWorksheet()->getDataValidation($this->getCoordinate());
388
    }
389
390
    /**
391
     * Set Data validation rules.
392
     *
393
     * @param DataValidation $pDataValidation
394
     *
395
     * @throws Exception
396
     *
397
     * @return Cell
398
     */
399 View Code Duplication
    public function setDataValidation(DataValidation $pDataValidation = null)
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...
400
    {
401
        if (!isset($this->parent)) {
402
            throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
403
        }
404
405
        $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
406
407
        return $this->updateInCollection();
408
    }
409
410
    /**
411
     * Does this cell contain valid value?
412
     *
413
     * @return bool
414
     */
415 3
    public function hasValidValue()
416
    {
417 3
        $validator = new DataValidator();
418
419 3
        return $validator->isValid($this);
420
    }
421
422
    /**
423
     * Does this cell contain a Hyperlink?
424
     *
425
     * @throws Exception
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
     * @throws Exception
442
     *
443
     * @return Hyperlink
444
     */
445 21
    public function getHyperlink()
446
    {
447 21
        if (!isset($this->parent)) {
448
            throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
449
        }
450
451 21
        return $this->getWorksheet()->getHyperlink($this->getCoordinate());
452
    }
453
454
    /**
455
     * Set Hyperlink.
456
     *
457
     * @param Hyperlink $pHyperlink
458
     *
459
     * @throws Exception
460
     *
461
     * @return Cell
462
     */
463 View Code Duplication
    public function setHyperlink(Hyperlink $pHyperlink = null)
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...
464
    {
465
        if (!isset($this->parent)) {
466
            throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
467
        }
468
469
        $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
470
471
        return $this->updateInCollection();
472
    }
473
474
    /**
475
     * Get cell collection.
476
     *
477
     * @return Cells
478
     */
479 64
    public function getParent()
480
    {
481 64
        return $this->parent;
482
    }
483
484
    /**
485
     * Get parent worksheet.
486
     *
487
     * @return Worksheet
488
     */
489 80
    public function getWorksheet()
490
    {
491 80
        return $this->parent->getParent();
492
    }
493
494
    /**
495
     * Is this cell in a merge range.
496
     *
497
     * @return bool
498
     */
499
    public function isInMergeRange()
500
    {
501
        return (bool) $this->getMergeRange();
502
    }
503
504
    /**
505
     * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
506
     *
507
     * @return bool
508
     */
509
    public function isMergeRangeValueCell()
510
    {
511
        if ($mergeRange = $this->getMergeRange()) {
512
            $mergeRange = Coordinate::splitRange($mergeRange);
0 ignored issues
show
Documentation introduced by
$mergeRange is of type array, but the function expects a string.

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...
513
            list($startCell) = $mergeRange[0];
514
            if ($this->getCoordinate() === $startCell) {
515
                return true;
516
            }
517
        }
518
519
        return false;
520
    }
521
522
    /**
523
     * If this cell is in a merge range, then return the range.
524
     *
525
     * @return string
526
     */
527
    public function getMergeRange()
528
    {
529
        foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
530
            if ($this->isInRange($mergeRange)) {
0 ignored issues
show
Documentation introduced by
$mergeRange is of type array, but the function expects a string.

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...
531
                return $mergeRange;
532
            }
533
        }
534
535
        return false;
536
    }
537
538
    /**
539
     * Get cell style.
540
     *
541
     * @return Style
542
     */
543 6
    public function getStyle()
544
    {
545 6
        return $this->getWorksheet()->getStyle($this->getCoordinate());
546
    }
547
548
    /**
549
     * Re-bind parent.
550
     *
551
     * @param Worksheet $parent
552
     *
553
     * @return Cell
554
     */
555
    public function rebindParent(Worksheet $parent)
556
    {
557
        $this->parent = $parent->getCellCollection();
558
559
        return $this->updateInCollection();
560
    }
561
562
    /**
563
     *    Is cell in a specific range?
564
     *
565
     * @param string $pRange Cell range (e.g. A1:A1)
566
     *
567
     * @return bool
568
     */
569
    public function isInRange($pRange)
570
    {
571
        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($pRange);
572
573
        // Translate properties
574
        $myColumn = Coordinate::columnIndexFromString($this->getColumn());
575
        $myRow = $this->getRow();
576
577
        // Verify if cell is in range
578
        return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
579
                ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
580
    }
581
582
    /**
583
     * Compare 2 cells.
584
     *
585
     * @param Cell $a Cell a
586
     * @param Cell $b Cell b
587
     *
588
     * @return int Result of comparison (always -1 or 1, never zero!)
589
     */
590
    public static function compareCells(Cell $a, Cell $b)
591
    {
592
        if ($a->getRow() < $b->getRow()) {
593
            return -1;
594
        } elseif ($a->getRow() > $b->getRow()) {
595
            return 1;
596
        } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) {
597
            return -1;
598
        }
599
600
        return 1;
601
    }
602
603
    /**
604
     * Get value binder to use.
605
     *
606
     * @return IValueBinder
607
     */
608 79
    public static function getValueBinder()
609
    {
610 79
        if (self::$valueBinder === null) {
611 71
            self::$valueBinder = new DefaultValueBinder();
612
        }
613
614 79
        return self::$valueBinder;
615
    }
616
617
    /**
618
     * Set value binder to use.
619
     *
620
     * @param IValueBinder $binder
621
     *
622
     * @throws Exception
623
     */
624 2
    public static function setValueBinder(IValueBinder $binder)
625
    {
626 2
        self::$valueBinder = $binder;
627 2
    }
628
629
    /**
630
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
631
     */
632 2 View Code Duplication
    public function __clone()
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...
633
    {
634 2
        $vars = get_object_vars($this);
635 2
        foreach ($vars as $key => $value) {
636 2
            if ((is_object($value)) && ($key != 'parent')) {
637
                $this->$key = clone $value;
638
            } else {
639 2
                $this->$key = $value;
640
            }
641
        }
642 2
    }
643
644
    /**
645
     * Get index to cellXf.
646
     *
647
     * @return int
648
     */
649 99
    public function getXfIndex()
650
    {
651 99
        return $this->xfIndex;
652
    }
653
654
    /**
655
     * Set index to cellXf.
656
     *
657
     * @param int $pValue
658
     *
659
     * @return Cell
660
     */
661 92
    public function setXfIndex($pValue)
662
    {
663 92
        $this->xfIndex = $pValue;
664
665 92
        return $this->updateInCollection();
666
    }
667
668
    /**
669
     * Set the formula attributes.
670
     *
671
     * @param mixed $pAttributes
672
     */
673
    public function setFormulaAttributes($pAttributes)
674
    {
675
        $this->formulaAttributes = $pAttributes;
676
677
        return $this;
678
    }
679
680
    /**
681
     * Get the formula attributes.
682
     */
683 18
    public function getFormulaAttributes()
684
    {
685 18
        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