Completed
Push — develop ( 148bee...a089a8 )
by Adrien
32:42
created

Cell::hasValidValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
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 149
    public function updateInCollection()
73
    {
74 149
        $this->parent->update($this);
75
76 149
        return $this;
77
    }
78
79 123
    public function detach()
80
    {
81 123
        $this->parent = null;
82 123
    }
83
84 111
    public function attach(Cells $parent)
85
    {
86 111
        $this->parent = $parent;
87 111
    }
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 156
    public function __construct($pValue, $pDataType, Worksheet $pSheet)
99
    {
100
        // Initialise cell value
101 156
        $this->value = $pValue;
102
103
        // Set worksheet cache
104 156
        $this->parent = $pSheet->getCellCollection();
105
106
        // Set datatype?
107 156
        if ($pDataType !== null) {
0 ignored issues
show
introduced by
The condition $pDataType !== null can never be false.
Loading history...
108 156
            if ($pDataType == DataType::TYPE_STRING2) {
109
                $pDataType = DataType::TYPE_STRING;
110
            }
111 156
            $this->dataType = $pDataType;
112
        } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
113
            throw new Exception('Value could not be bound to cell.');
114
        }
115 156
    }
116
117
    /**
118
     * Get cell coordinate column.
119
     *
120
     * @return string
121
     */
122 52
    public function getColumn()
123
    {
124 52
        return $this->parent->getCurrentColumn();
125
    }
126
127
    /**
128
     * Get cell coordinate row.
129
     *
130
     * @return int
131
     */
132 49
    public function getRow()
133
    {
134 49
        return $this->parent->getCurrentRow();
135
    }
136
137
    /**
138
     * Get cell coordinate.
139
     *
140
     * @return string
141
     */
142 153
    public function getCoordinate()
143
    {
144 153
        return $this->parent->getCurrentCoordinate();
145
    }
146
147
    /**
148
     * Get cell value.
149
     *
150
     * @return mixed
151
     */
152 140
    public function getValue()
153
    {
154 140
        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 106
    public function setValue($pValue)
183
    {
184 106
        if (!self::getValueBinder()->bindValue($this, $pValue)) {
185
            throw new Exception('Value could not be bound to cell.');
186
        }
187
188 106
        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 145
    public function setValueExplicit($pValue, $pDataType)
202
    {
203
        // set the value according to data type
204
        switch ($pDataType) {
205 145
            case DataType::TYPE_NULL:
206 30
                $this->value = $pValue;
207
208 30
                break;
209 145
            case DataType::TYPE_STRING2:
210
                $pDataType = DataType::TYPE_STRING;
211
                // no break
212 145
            case DataType::TYPE_STRING:
213
                // Synonym for string
214 115
            case DataType::TYPE_INLINE:
215
                // Rich text
216 119
                $this->value = DataType::checkString($pValue);
217
218 119
                break;
219 114
            case DataType::TYPE_NUMERIC:
220 103
                $this->value = (float) $pValue;
221
222 103
                break;
223 65
            case DataType::TYPE_FORMULA:
224 61
                $this->value = (string) $pValue;
225
226 61
                break;
227 27
            case DataType::TYPE_BOOL:
228 27
                $this->value = (bool) $pValue;
229
230 27
                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
238
                break;
239
        }
240
241
        // set the datatype
242 145
        $this->dataType = $pDataType;
243
244 145
        return $this->updateInCollection();
245
    }
246
247
    /**
248
     * Get calculated cell value.
249
     *
250
     * @param bool $resetLog Whether the calculation engine logger should be reset or not
251
     *
252
     * @throws Exception
253
     *
254
     * @return mixed
255
     */
256 93
    public function getCalculatedValue($resetLog = true)
257
    {
258 93
        if ($this->dataType == DataType::TYPE_FORMULA) {
259
            try {
260 51
                $result = Calculation::getInstance(
261 51
                    $this->getWorksheet()->getParent()
262 51
                )->calculateCellValue($this, $resetLog);
263
                //    We don't yet handle array returns
264 51
                if (is_array($result)) {
265 51
                    while (is_array($result)) {
266 10
                        $result = array_pop($result);
267
                    }
268
                }
269
            } catch (Exception $ex) {
270
                if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
271
                    return $this->calculatedValue; // Fallback for calculations referencing external files.
272
                }
273
274
                throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
275
                    $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
276
                );
277
            }
278
279 51
            if ($result === '#Not Yet Implemented') {
280
                return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
281
            }
282
283 51
            return $result;
284 90
        } elseif ($this->value instanceof RichText) {
285 2
            return $this->value->getPlainText();
286
        }
287
288 90
        return $this->value;
289
    }
290
291
    /**
292
     * Set old calculated value (cached).
293
     *
294
     * @param mixed $pValue Value
295
     *
296
     * @return Cell
297
     */
298 25
    public function setCalculatedValue($pValue)
299
    {
300 25
        if ($pValue !== null) {
301 25
            $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
302
        }
303
304 25
        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 96
    public function getDataType()
328
    {
329 96
        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
    public function isFormula()
355
    {
356
        return $this->dataType == DataType::TYPE_FORMULA;
357
    }
358
359
    /**
360
     *    Does this cell contain Data validation rules?
361
     *
362
     * @throws Exception
363
     *
364
     * @return bool
365
     */
366 3
    public function hasDataValidation()
367
    {
368 3
        if (!isset($this->parent)) {
369
            throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
370
        }
371
372 3
        return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
373
    }
374
375
    /**
376
     * Get Data validation rules.
377
     *
378
     * @throws Exception
379
     *
380
     * @return DataValidation
381
     */
382 4
    public function getDataValidation()
383
    {
384 4
        if (!isset($this->parent)) {
385
            throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
386
        }
387
388 4
        return $this->getWorksheet()->getDataValidation($this->getCoordinate());
389
    }
390
391
    /**
392
     * Set Data validation rules.
393
     *
394
     * @param DataValidation $pDataValidation
395
     *
396
     * @throws Exception
397
     *
398
     * @return Cell
399
     */
400
    public function setDataValidation(DataValidation $pDataValidation = null)
401
    {
402
        if (!isset($this->parent)) {
403
            throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
404
        }
405
406
        $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
407
408
        return $this->updateInCollection();
409
    }
410
411
    /**
412
     * Does this cell contain valid value?
413
     *
414
     * @return bool
415
     */
416 3
    public function hasValidValue()
417
    {
418 3
        $validator = new DataValidator();
419
420 3
        return $validator->isValid($this);
421
    }
422
423
    /**
424
     * Does this cell contain a Hyperlink?
425
     *
426
     * @throws Exception
427
     *
428
     * @return bool
429
     */
430
    public function hasHyperlink()
431
    {
432
        if (!isset($this->parent)) {
433
            throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
434
        }
435
436
        return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
437
    }
438
439
    /**
440
     * Get Hyperlink.
441
     *
442
     * @throws Exception
443
     *
444
     * @return Hyperlink
445
     */
446 21
    public function getHyperlink()
447
    {
448 21
        if (!isset($this->parent)) {
449
            throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
450
        }
451
452 21
        return $this->getWorksheet()->getHyperlink($this->getCoordinate());
453
    }
454
455
    /**
456
     * Set Hyperlink.
457
     *
458
     * @param Hyperlink $pHyperlink
459
     *
460
     * @throws Exception
461
     *
462
     * @return Cell
463
     */
464
    public function setHyperlink(Hyperlink $pHyperlink = null)
465
    {
466
        if (!isset($this->parent)) {
467
            throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
468
        }
469
470
        $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
471
472
        return $this->updateInCollection();
473
    }
474
475
    /**
476
     * Get cell collection.
477
     *
478
     * @return Cells
479
     */
480 74
    public function getParent()
481
    {
482 74
        return $this->parent;
483
    }
484
485
    /**
486
     * Get parent worksheet.
487
     *
488
     * @return Worksheet
489
     */
490 85
    public function getWorksheet()
491
    {
492 85
        return $this->parent->getParent();
493
    }
494
495
    /**
496
     * Is this cell in a merge range.
497
     *
498
     * @return bool
499
     */
500
    public function isInMergeRange()
501
    {
502
        return (bool) $this->getMergeRange();
503
    }
504
505
    /**
506
     * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
507
     *
508
     * @return bool
509
     */
510 4
    public function isMergeRangeValueCell()
511
    {
512 4
        if ($mergeRange = $this->getMergeRange()) {
513 1
            $mergeRange = Coordinate::splitRange($mergeRange);
514 1
            list($startCell) = $mergeRange[0];
515 1
            if ($this->getCoordinate() === $startCell) {
516 1
                return true;
517
            }
518
        }
519
520 4
        return false;
521
    }
522
523
    /**
524
     * If this cell is in a merge range, then return the range.
525
     *
526
     * @return string
527
     */
528 4
    public function getMergeRange()
529
    {
530 4
        foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
531 1
            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

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