Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Cell;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
   6  use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
   7  use PhpOffice\PhpSpreadsheet\Collection\Cells;
   8  use PhpOffice\PhpSpreadsheet\Exception;
   9  use PhpOffice\PhpSpreadsheet\RichText\RichText;
  10  use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
  11  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
  12  use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor;
  13  use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  14  use PhpOffice\PhpSpreadsheet\Style\Style;
  15  use PhpOffice\PhpSpreadsheet\Worksheet\Table;
  16  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  17  
  18  class Cell
  19  {
  20      /**
  21       * Value binder to use.
  22       *
  23       * @var IValueBinder
  24       */
  25      private static $valueBinder;
  26  
  27      /**
  28       * Value of the cell.
  29       *
  30       * @var mixed
  31       */
  32      private $value;
  33  
  34      /**
  35       *    Calculated value of the cell (used for caching)
  36       *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
  37       *        create the original spreadsheet file.
  38       *    Note that this value is not guaranteed to reflect the actual calculated value because it is
  39       *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
  40       *        values used by the formula have changed since it was last calculated.
  41       *
  42       * @var mixed
  43       */
  44      private $calculatedValue;
  45  
  46      /**
  47       * Type of the cell data.
  48       *
  49       * @var string
  50       */
  51      private $dataType;
  52  
  53      /**
  54       * The collection of cells that this cell belongs to (i.e. The Cell Collection for the parent Worksheet).
  55       *
  56       * @var ?Cells
  57       */
  58      private $parent;
  59  
  60      /**
  61       * Index to the cellXf reference for the styling of this cell.
  62       *
  63       * @var int
  64       */
  65      private $xfIndex = 0;
  66  
  67      /**
  68       * Attributes of the formula.
  69       *
  70       * @var mixed
  71       */
  72      private $formulaAttributes;
  73  
  74      /** @var IgnoredErrors */
  75      private $ignoredErrors;
  76  
  77      /**
  78       * Update the cell into the cell collection.
  79       *
  80       * @return $this
  81       */
  82      public function updateInCollection(): self
  83      {
  84          $parent = $this->parent;
  85          if ($parent === null) {
  86              throw new Exception('Cannot update when cell is not bound to a worksheet');
  87          }
  88          $parent->update($this);
  89  
  90          return $this;
  91      }
  92  
  93      public function detach(): void
  94      {
  95          $this->parent = null;
  96      }
  97  
  98      public function attach(Cells $parent): void
  99      {
 100          $this->parent = $parent;
 101      }
 102  
 103      /**
 104       * Create a new Cell.
 105       *
 106       * @param mixed $value
 107       */
 108      public function __construct($value, ?string $dataType, Worksheet $worksheet)
 109      {
 110          // Initialise cell value
 111          $this->value = $value;
 112  
 113          // Set worksheet cache
 114          $this->parent = $worksheet->getCellCollection();
 115  
 116          // Set datatype?
 117          if ($dataType !== null) {
 118              if ($dataType == DataType::TYPE_STRING2) {
 119                  $dataType = DataType::TYPE_STRING;
 120              }
 121              $this->dataType = $dataType;
 122          } elseif (self::getValueBinder()->bindValue($this, $value) === false) {
 123              throw new Exception('Value could not be bound to cell.');
 124          }
 125          $this->ignoredErrors = new IgnoredErrors();
 126      }
 127  
 128      /**
 129       * Get cell coordinate column.
 130       *
 131       * @return string
 132       */
 133      public function getColumn()
 134      {
 135          $parent = $this->parent;
 136          if ($parent === null) {
 137              throw new Exception('Cannot get column when cell is not bound to a worksheet');
 138          }
 139  
 140          return $parent->getCurrentColumn();
 141      }
 142  
 143      /**
 144       * Get cell coordinate row.
 145       *
 146       * @return int
 147       */
 148      public function getRow()
 149      {
 150          $parent = $this->parent;
 151          if ($parent === null) {
 152              throw new Exception('Cannot get row when cell is not bound to a worksheet');
 153          }
 154  
 155          return $parent->getCurrentRow();
 156      }
 157  
 158      /**
 159       * Get cell coordinate.
 160       *
 161       * @return string
 162       */
 163      public function getCoordinate()
 164      {
 165          $parent = $this->parent;
 166          if ($parent !== null) {
 167              $coordinate = $parent->getCurrentCoordinate();
 168          } else {
 169              $coordinate = null;
 170          }
 171          if ($coordinate === null) {
 172              throw new Exception('Coordinate no longer exists');
 173          }
 174  
 175          return $coordinate;
 176      }
 177  
 178      /**
 179       * Get cell value.
 180       *
 181       * @return mixed
 182       */
 183      public function getValue()
 184      {
 185          return $this->value;
 186      }
 187  
 188      /**
 189       * Get cell value with formatting.
 190       */
 191      public function getFormattedValue(): string
 192      {
 193          return (string) NumberFormat::toFormattedString(
 194              $this->getCalculatedValue(),
 195              (string) $this->getStyle()->getNumberFormat()->getFormatCode()
 196          );
 197      }
 198  
 199      /**
 200       * @param mixed $oldValue
 201       * @param mixed $newValue
 202       */
 203      protected static function updateIfCellIsTableHeader(?Worksheet $workSheet, self $cell, $oldValue, $newValue): void
 204      {
 205          if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '') || $workSheet === null) {
 206              return;
 207          }
 208  
 209          foreach ($workSheet->getTableCollection() as $table) {
 210              /** @var Table $table */
 211              if ($cell->isInRange($table->getRange())) {
 212                  $rangeRowsColumns = Coordinate::getRangeBoundaries($table->getRange());
 213                  if ($cell->getRow() === (int) $rangeRowsColumns[0][1]) {
 214                      Table\Column::updateStructuredReferences($workSheet, $oldValue, $newValue);
 215                  }
 216  
 217                  return;
 218              }
 219          }
 220      }
 221  
 222      /**
 223       * Set cell value.
 224       *
 225       *    Sets the value for a cell, automatically determining the datatype using the value binder
 226       *
 227       * @param mixed $value Value
 228       * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder
 229       *
 230       * @throws Exception
 231       *
 232       * @return $this
 233       */
 234      public function setValue($value, ?IValueBinder $binder = null): self
 235      {
 236          $binder ??= self::getValueBinder();
 237          if (!$binder->bindValue($this, $value)) {
 238              throw new Exception('Value could not be bound to cell.');
 239          }
 240  
 241          return $this;
 242      }
 243  
 244      /**
 245       * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
 246       *
 247       * @param mixed $value Value
 248       * @param string $dataType Explicit data type, see DataType::TYPE_*
 249       *        Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this
 250       *             method, then it is your responsibility as an end-user developer to validate that the value and
 251       *             the datatype match.
 252       *       If you do mismatch value and datatype, then the value you enter may be changed to match the datatype
 253       *          that you specify.
 254       *
 255       * @return Cell
 256       */
 257      public function setValueExplicit($value, string $dataType = DataType::TYPE_STRING)
 258      {
 259          $oldValue = $this->value;
 260  
 261          // set the value according to data type
 262          switch ($dataType) {
 263              case DataType::TYPE_NULL:
 264                  $this->value = null;
 265  
 266                  break;
 267              case DataType::TYPE_STRING2:
 268                  $dataType = DataType::TYPE_STRING;
 269                  // no break
 270              case DataType::TYPE_STRING:
 271                  // Synonym for string
 272              case DataType::TYPE_INLINE:
 273                  // Rich text
 274                  $this->value = DataType::checkString($value);
 275  
 276                  break;
 277              case DataType::TYPE_NUMERIC:
 278                  if (is_string($value) && !is_numeric($value)) {
 279                      throw new Exception('Invalid numeric value for datatype Numeric');
 280                  }
 281                  $this->value = 0 + $value;
 282  
 283                  break;
 284              case DataType::TYPE_FORMULA:
 285                  $this->value = (string) $value;
 286  
 287                  break;
 288              case DataType::TYPE_BOOL:
 289                  $this->value = (bool) $value;
 290  
 291                  break;
 292              case DataType::TYPE_ISO_DATE:
 293                  $this->value = SharedDate::convertIsoDate($value);
 294                  $dataType = DataType::TYPE_NUMERIC;
 295  
 296                  break;
 297              case DataType::TYPE_ERROR:
 298                  $this->value = DataType::checkErrorCode($value);
 299  
 300                  break;
 301              default:
 302                  throw new Exception('Invalid datatype: ' . $dataType);
 303          }
 304  
 305          // set the datatype
 306          $this->dataType = $dataType;
 307  
 308          $this->updateInCollection();
 309          $cellCoordinate = $this->getCoordinate();
 310          self::updateIfCellIsTableHeader($this->getParent()->getParent(), $this, $oldValue, $value); // @phpstan-ignore-line
 311  
 312          return $this->getParent()->get($cellCoordinate); // @phpstan-ignore-line
 313      }
 314  
 315      public const CALCULATE_DATE_TIME_ASIS = 0;
 316      public const CALCULATE_DATE_TIME_FLOAT = 1;
 317      public const CALCULATE_TIME_FLOAT = 2;
 318  
 319      /** @var int */
 320      private static $calculateDateTimeType = self::CALCULATE_DATE_TIME_ASIS;
 321  
 322      public static function getCalculateDateTimeType(): int
 323      {
 324          return self::$calculateDateTimeType;
 325      }
 326  
 327      public static function setCalculateDateTimeType(int $calculateDateTimeType): void
 328      {
 329          switch ($calculateDateTimeType) {
 330              case self::CALCULATE_DATE_TIME_ASIS:
 331              case self::CALCULATE_DATE_TIME_FLOAT:
 332              case self::CALCULATE_TIME_FLOAT:
 333                  self::$calculateDateTimeType = $calculateDateTimeType;
 334  
 335                  break;
 336              default:
 337                  throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception("Invalid value $calculateDateTimeType for calculated date time type");
 338          }
 339      }
 340  
 341      /**
 342       * Convert date, time, or datetime from int to float if desired.
 343       *
 344       * @param mixed $result
 345       *
 346       * @return mixed
 347       */
 348      private function convertDateTimeInt($result)
 349      {
 350          if (is_int($result)) {
 351              if (self::$calculateDateTimeType === self::CALCULATE_TIME_FLOAT) {
 352                  if (SharedDate::isDateTime($this, $result, false)) {
 353                      $result = (float) $result;
 354                  }
 355              } elseif (self::$calculateDateTimeType === self::CALCULATE_DATE_TIME_FLOAT) {
 356                  if (SharedDate::isDateTime($this, $result, true)) {
 357                      $result = (float) $result;
 358                  }
 359              }
 360          }
 361  
 362          return $result;
 363      }
 364  
 365      /**
 366       * Get calculated cell value.
 367       *
 368       * @param bool $resetLog Whether the calculation engine logger should be reset or not
 369       *
 370       * @return mixed
 371       */
 372      public function getCalculatedValue(bool $resetLog = true)
 373      {
 374          if ($this->dataType === DataType::TYPE_FORMULA) {
 375              try {
 376                  $index = $this->getWorksheet()->getParentOrThrow()->getActiveSheetIndex();
 377                  $selected = $this->getWorksheet()->getSelectedCells();
 378                  $result = Calculation::getInstance(
 379                      $this->getWorksheet()->getParent()
 380                  )->calculateCellValue($this, $resetLog);
 381                  $result = $this->convertDateTimeInt($result);
 382                  $this->getWorksheet()->setSelectedCells($selected);
 383                  $this->getWorksheet()->getParentOrThrow()->setActiveSheetIndex($index);
 384                  //    We don't yet handle array returns
 385                  if (is_array($result)) {
 386                      while (is_array($result)) {
 387                          $result = array_shift($result);
 388                      }
 389                  }
 390              } catch (Exception $ex) {
 391                  if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
 392                      return $this->calculatedValue; // Fallback for calculations referencing external files.
 393                  } elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) {
 394                      return ExcelError::NAME();
 395                  }
 396  
 397                  throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
 398                      $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage(),
 399                      $ex->getCode(),
 400                      $ex
 401                  );
 402              }
 403  
 404              if ($result === '#Not Yet Implemented') {
 405                  return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
 406              }
 407  
 408              return $result;
 409          } elseif ($this->value instanceof RichText) {
 410              return $this->value->getPlainText();
 411          }
 412  
 413          return $this->convertDateTimeInt($this->value);
 414      }
 415  
 416      /**
 417       * Set old calculated value (cached).
 418       *
 419       * @param mixed $originalValue Value
 420       */
 421      public function setCalculatedValue($originalValue): self
 422      {
 423          if ($originalValue !== null) {
 424              $this->calculatedValue = (is_numeric($originalValue)) ? (float) $originalValue : $originalValue;
 425          }
 426  
 427          return $this->updateInCollection();
 428      }
 429  
 430      /**
 431       *    Get old calculated value (cached)
 432       *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
 433       *        create the original spreadsheet file.
 434       *    Note that this value is not guaranteed to reflect the actual calculated value because it is
 435       *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
 436       *        values used by the formula have changed since it was last calculated.
 437       *
 438       * @return mixed
 439       */
 440      public function getOldCalculatedValue()
 441      {
 442          return $this->calculatedValue;
 443      }
 444  
 445      /**
 446       * Get cell data type.
 447       */
 448      public function getDataType(): string
 449      {
 450          return $this->dataType;
 451      }
 452  
 453      /**
 454       * Set cell data type.
 455       *
 456       * @param string $dataType see DataType::TYPE_*
 457       */
 458      public function setDataType($dataType): self
 459      {
 460          if ($dataType == DataType::TYPE_STRING2) {
 461              $dataType = DataType::TYPE_STRING;
 462          }
 463          $this->dataType = $dataType;
 464  
 465          return $this->updateInCollection();
 466      }
 467  
 468      /**
 469       * Identify if the cell contains a formula.
 470       */
 471      public function isFormula(): bool
 472      {
 473          return $this->dataType === DataType::TYPE_FORMULA && $this->getStyle()->getQuotePrefix() === false;
 474      }
 475  
 476      /**
 477       *    Does this cell contain Data validation rules?
 478       */
 479      public function hasDataValidation(): bool
 480      {
 481          if (!isset($this->parent)) {
 482              throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
 483          }
 484  
 485          return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
 486      }
 487  
 488      /**
 489       * Get Data validation rules.
 490       */
 491      public function getDataValidation(): DataValidation
 492      {
 493          if (!isset($this->parent)) {
 494              throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
 495          }
 496  
 497          return $this->getWorksheet()->getDataValidation($this->getCoordinate());
 498      }
 499  
 500      /**
 501       * Set Data validation rules.
 502       */
 503      public function setDataValidation(?DataValidation $dataValidation = null): self
 504      {
 505          if (!isset($this->parent)) {
 506              throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
 507          }
 508  
 509          $this->getWorksheet()->setDataValidation($this->getCoordinate(), $dataValidation);
 510  
 511          return $this->updateInCollection();
 512      }
 513  
 514      /**
 515       * Does this cell contain valid value?
 516       */
 517      public function hasValidValue(): bool
 518      {
 519          $validator = new DataValidator();
 520  
 521          return $validator->isValid($this);
 522      }
 523  
 524      /**
 525       * Does this cell contain a Hyperlink?
 526       */
 527      public function hasHyperlink(): bool
 528      {
 529          if (!isset($this->parent)) {
 530              throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
 531          }
 532  
 533          return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
 534      }
 535  
 536      /**
 537       * Get Hyperlink.
 538       */
 539      public function getHyperlink(): Hyperlink
 540      {
 541          if (!isset($this->parent)) {
 542              throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
 543          }
 544  
 545          return $this->getWorksheet()->getHyperlink($this->getCoordinate());
 546      }
 547  
 548      /**
 549       * Set Hyperlink.
 550       */
 551      public function setHyperlink(?Hyperlink $hyperlink = null): self
 552      {
 553          if (!isset($this->parent)) {
 554              throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
 555          }
 556  
 557          $this->getWorksheet()->setHyperlink($this->getCoordinate(), $hyperlink);
 558  
 559          return $this->updateInCollection();
 560      }
 561  
 562      /**
 563       * Get cell collection.
 564       *
 565       * @return ?Cells
 566       */
 567      public function getParent()
 568      {
 569          return $this->parent;
 570      }
 571  
 572      /**
 573       * Get parent worksheet.
 574       */
 575      public function getWorksheet(): Worksheet
 576      {
 577          $parent = $this->parent;
 578          if ($parent !== null) {
 579              $worksheet = $parent->getParent();
 580          } else {
 581              $worksheet = null;
 582          }
 583  
 584          if ($worksheet === null) {
 585              throw new Exception('Worksheet no longer exists');
 586          }
 587  
 588          return $worksheet;
 589      }
 590  
 591      public function getWorksheetOrNull(): ?Worksheet
 592      {
 593          $parent = $this->parent;
 594          if ($parent !== null) {
 595              $worksheet = $parent->getParent();
 596          } else {
 597              $worksheet = null;
 598          }
 599  
 600          return $worksheet;
 601      }
 602  
 603      /**
 604       * Is this cell in a merge range.
 605       */
 606      public function isInMergeRange(): bool
 607      {
 608          return (bool) $this->getMergeRange();
 609      }
 610  
 611      /**
 612       * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
 613       */
 614      public function isMergeRangeValueCell(): bool
 615      {
 616          if ($mergeRange = $this->getMergeRange()) {
 617              $mergeRange = Coordinate::splitRange($mergeRange);
 618              [$startCell] = $mergeRange[0];
 619  
 620              return $this->getCoordinate() === $startCell;
 621          }
 622  
 623          return false;
 624      }
 625  
 626      /**
 627       * If this cell is in a merge range, then return the range.
 628       *
 629       * @return false|string
 630       */
 631      public function getMergeRange()
 632      {
 633          foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
 634              if ($this->isInRange($mergeRange)) {
 635                  return $mergeRange;
 636              }
 637          }
 638  
 639          return false;
 640      }
 641  
 642      /**
 643       * Get cell style.
 644       */
 645      public function getStyle(): Style
 646      {
 647          return $this->getWorksheet()->getStyle($this->getCoordinate());
 648      }
 649  
 650      /**
 651       * Get cell style.
 652       */
 653      public function getAppliedStyle(): Style
 654      {
 655          if ($this->getWorksheet()->conditionalStylesExists($this->getCoordinate()) === false) {
 656              return $this->getStyle();
 657          }
 658          $range = $this->getWorksheet()->getConditionalRange($this->getCoordinate());
 659          if ($range === null) {
 660              return $this->getStyle();
 661          }
 662  
 663          $matcher = new CellStyleAssessor($this, $range);
 664  
 665          return $matcher->matchConditions($this->getWorksheet()->getConditionalStyles($this->getCoordinate()));
 666      }
 667  
 668      /**
 669       * Re-bind parent.
 670       */
 671      public function rebindParent(Worksheet $parent): self
 672      {
 673          $this->parent = $parent->getCellCollection();
 674  
 675          return $this->updateInCollection();
 676      }
 677  
 678      /**
 679       *    Is cell in a specific range?
 680       *
 681       * @param string $range Cell range (e.g. A1:A1)
 682       */
 683      public function isInRange(string $range): bool
 684      {
 685          [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range);
 686  
 687          // Translate properties
 688          $myColumn = Coordinate::columnIndexFromString($this->getColumn());
 689          $myRow = $this->getRow();
 690  
 691          // Verify if cell is in range
 692          return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
 693                  ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
 694      }
 695  
 696      /**
 697       * Compare 2 cells.
 698       *
 699       * @param Cell $a Cell a
 700       * @param Cell $b Cell b
 701       *
 702       * @return int Result of comparison (always -1 or 1, never zero!)
 703       */
 704      public static function compareCells(self $a, self $b): int
 705      {
 706          if ($a->getRow() < $b->getRow()) {
 707              return -1;
 708          } elseif ($a->getRow() > $b->getRow()) {
 709              return 1;
 710          } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) {
 711              return -1;
 712          }
 713  
 714          return 1;
 715      }
 716  
 717      /**
 718       * Get value binder to use.
 719       */
 720      public static function getValueBinder(): IValueBinder
 721      {
 722          if (self::$valueBinder === null) {
 723              self::$valueBinder = new DefaultValueBinder();
 724          }
 725  
 726          return self::$valueBinder;
 727      }
 728  
 729      /**
 730       * Set value binder to use.
 731       */
 732      public static function setValueBinder(IValueBinder $binder): void
 733      {
 734          self::$valueBinder = $binder;
 735      }
 736  
 737      /**
 738       * Implement PHP __clone to create a deep clone, not just a shallow copy.
 739       */
 740      public function __clone()
 741      {
 742          $vars = get_object_vars($this);
 743          foreach ($vars as $propertyName => $propertyValue) {
 744              if ((is_object($propertyValue)) && ($propertyName !== 'parent')) {
 745                  $this->$propertyName = clone $propertyValue;
 746              } else {
 747                  $this->$propertyName = $propertyValue;
 748              }
 749          }
 750      }
 751  
 752      /**
 753       * Get index to cellXf.
 754       */
 755      public function getXfIndex(): int
 756      {
 757          return $this->xfIndex;
 758      }
 759  
 760      /**
 761       * Set index to cellXf.
 762       */
 763      public function setXfIndex(int $indexValue): self
 764      {
 765          $this->xfIndex = $indexValue;
 766  
 767          return $this->updateInCollection();
 768      }
 769  
 770      /**
 771       * Set the formula attributes.
 772       *
 773       * @param mixed $attributes
 774       *
 775       * @return $this
 776       */
 777      public function setFormulaAttributes($attributes): self
 778      {
 779          $this->formulaAttributes = $attributes;
 780  
 781          return $this;
 782      }
 783  
 784      /**
 785       * Get the formula attributes.
 786       *
 787       * @return mixed
 788       */
 789      public function getFormulaAttributes()
 790      {
 791          return $this->formulaAttributes;
 792      }
 793  
 794      /**
 795       * Convert to string.
 796       *
 797       * @return string
 798       */
 799      public function __toString()
 800      {
 801          return (string) $this->getValue();
 802      }
 803  
 804      public function getIgnoredErrors(): IgnoredErrors
 805      {
 806          return $this->ignoredErrors;
 807      }
 808  }