Failed Conditions
Push — master ( 59326f...691b68 )
by Adrien
25:51 queued 17:10
created

Cell::getFormattedValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 1
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 120
    public function updateInCollection()
73
    {
74 120
        $this->parent->update($this);
75
76 120
        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 124
    public function __construct($pValue, $pDataType, Worksheet $pSheet)
99
    {
100
        // Initialise cell value
101 124
        $this->value = $pValue;
102
103
        // Set worksheet cache
104 124
        $this->parent = $pSheet->getCellCollection();
105
106
        // Set datatype?
107 124
        if ($pDataType !== null) {
108 124
            if ($pDataType == DataType::TYPE_STRING2) {
109
                $pDataType = DataType::TYPE_STRING;
110
            }
111 124
            $this->dataType = $pDataType;
112
        } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
113
            throw new Exception('Value could not be bound to cell.');
114
        }
115 124
    }
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 124
    public function getCoordinate()
143
    {
144 124
        return $this->parent->getCurrentCoordinate();
145
    }
146
147
    /**
148
     * Get cell value.
149
     *
150
     * @return mixed
151
     */
152 108
    public function getValue()
153
    {
154 108
        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 78
    public function setValue($pValue)
183
    {
184 78
        if (!self::getValueBinder()->bindValue($this, $pValue)) {
185
            throw new Exception('Value could not be bound to cell.');
186
        }
187
188 78
        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 116
    public function setValueExplicit($pValue, $pDataType)
202
    {
203
        // set the value according to data type
204
        switch ($pDataType) {
205 116
            case DataType::TYPE_NULL:
206 29
                $this->value = $pValue;
207
208 29
                break;
209 116
            case DataType::TYPE_STRING2:
210
                $pDataType = DataType::TYPE_STRING;
211
                // no break
212 116
            case DataType::TYPE_STRING:
213
                // Synonym for string
214 100
            case DataType::TYPE_INLINE:
215
                // Rich text
216 100
                $this->value = DataType::checkString($pValue);
217
218 100
                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;
238
        }
239
240
        // set the datatype
241 116
        $this->dataType = $pDataType;
242
243 116
        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 70
    public function getCalculatedValue($resetLog = true)
256
    {
257 70
        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 70
        } elseif ($this->value instanceof RichText) {
284 2
            return $this->value->getPlainText();
285
        }
286
287 70
        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 2
    public function isMergeRangeValueCell()
510
    {
511 2
        if ($mergeRange = $this->getMergeRange()) {
512
            $mergeRange = Coordinate::splitRange($mergeRange);
513
            list($startCell) = $mergeRange[0];
514
            if ($this->getCoordinate() === $startCell) {
515
                return true;
516
            }
517
        }
518
519 2
        return false;
520
    }
521
522
    /**
523
     * If this cell is in a merge range, then return the range.
524
     *
525
     * @return string
526
     */
527 2
    public function getMergeRange()
528
    {
529 2
        foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
530
            if ($this->isInRange($mergeRange)) {
0 ignored issues
show
Bug introduced by
$mergeRange of type array is incompatible with the type string expected by parameter $pRange of PhpOffice\PhpSpreadsheet\Cell\Cell::isInRange(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

530
            if ($this->isInRange(/** @scrutinizer ignore-type */ $mergeRange)) {
Loading history...
531
                return $mergeRange;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $mergeRange returns the type array which is incompatible with the documented return type string.
Loading history...
532
            }
533
        }
534
535 2
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
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 78
    public static function getValueBinder()
609
    {
610 78
        if (self::$valueBinder === null) {
611 71
            self::$valueBinder = new DefaultValueBinder();
612
        }
613
614 78
        return self::$valueBinder;
615
    }
616
617
    /**
618
     * Set value binder to use.
619
     *
620
     * @param IValueBinder $binder
621
     */
622 2
    public static function setValueBinder(IValueBinder $binder)
623
    {
624 2
        self::$valueBinder = $binder;
625 2
    }
626
627
    /**
628
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
629
     */
630 2
    public function __clone()
631
    {
632 2
        $vars = get_object_vars($this);
633 2
        foreach ($vars as $key => $value) {
634 2
            if ((is_object($value)) && ($key != 'parent')) {
635
                $this->$key = clone $value;
636
            } else {
637 2
                $this->$key = $value;
638
            }
639
        }
640 2
    }
641
642
    /**
643
     * Get index to cellXf.
644
     *
645
     * @return int
646
     */
647 98
    public function getXfIndex()
648
    {
649 98
        return $this->xfIndex;
650
    }
651
652
    /**
653
     * Set index to cellXf.
654
     *
655
     * @param int $pValue
656
     *
657
     * @return Cell
658
     */
659 92
    public function setXfIndex($pValue)
660
    {
661 92
        $this->xfIndex = $pValue;
662
663 92
        return $this->updateInCollection();
664
    }
665
666
    /**
667
     * Set the formula attributes.
668
     *
669
     * @param mixed $pAttributes
670
     */
671
    public function setFormulaAttributes($pAttributes)
672
    {
673
        $this->formulaAttributes = $pAttributes;
674
675
        return $this;
676
    }
677
678
    /**
679
     * Get the formula attributes.
680
     */
681 18
    public function getFormulaAttributes()
682
    {
683 18
        return $this->formulaAttributes;
684
    }
685
686
    /**
687
     * Convert to string.
688
     *
689
     * @return string
690
     */
691
    public function __toString()
692
    {
693
        return (string) $this->getValue();
694
    }
695
}
696