has($id)) { throw new FlexFieldPropertyNotFoundException('Flex property "' . $id . '" is not available.', 1731962637); } [$sheetName, $propertyPath] = $this->processId($id); if ($sheetName === '' && $this->hasMultipleSheets()) { // Get the sheet name for the requested property path - There is one, since has() returned true. foreach ($this->sheets as $name => $sheet) { if (ArrayUtility::isValidPath($sheet, $propertyPath, '.')) { $sheetName = $name; break; } } } $propertyValue = ArrayUtility::getValueByPath($this->sheets[$sheetName], $propertyPath, '.'); if (is_array($propertyValue)) { array_walk_recursive($propertyValue, fn(mixed &$value): mixed => $value = $this->resolveRecordPropertyClosure($propertyPath, $value)); } else { $propertyValue = $this->resolveRecordPropertyClosure($propertyPath, $propertyValue); } ArrayUtility::setValueByPath($this->sheets[$sheetName], $propertyPath, $propertyValue, '.'); return $propertyValue; } public function has(string $id): bool { // If there are no sheets, no value can be determined. if ($this->sheets === []) { return false; } [$sheetName, $propertyPath] = $this->processId($id); if ($sheetName !== '' && !isset($this->sheets[$sheetName])) { // Given sheet name does not exist return false; } if ($this->hasMultipleSheets()) { if ($sheetName !== '') { return ArrayUtility::isValidPath($this->sheets[$sheetName], $propertyPath, '.'); } // In case no sheet name is given, we try to execute fallback handling // by searching for the requested $propertyPath in all sheets. $occurences = []; foreach ($this->sheets as $sheetName => $sheet) { if (ArrayUtility::isValidPath($sheet, $propertyPath, '.')) { $occurences[$sheetName] = $propertyPath; } } if (count($occurences) > 1) { // This is a special case, which we handle with an exception to create awareness for the error. throw new FlexFieldPropertyException('Given id is ambigious since the field exists in multiple sheets and no sheet is defined.', 1731962638); } // Whether the requested $propertyPath name exist in a sheet return count($occurences) === 1; } // Standard case, whether the $propertyPath exists in the given sheet name return ArrayUtility::isValidPath($this->sheets[$sheetName], $propertyPath, '.'); } public function offsetExists(mixed $offset): bool { return $this->has($offset); } public function offsetGet(mixed $offset): mixed { return $this->get($offset); } public function offsetSet(mixed $offset, mixed $value): void { // Not implemented } public function offsetUnset(mixed $offset): void { // Not implemented } /** * Getter to be used in fluid for accessing field values */ public function getSheets(): array { return $this->sheets; } public function toArray(): array { return $this->getSheets(); } protected function hasMultipleSheets(): bool { return count($this->sheets) > 1; } protected function processId(string $id): array { if (str_contains($id, '/')) { // $id contains a sheet name return explode('/', $id, 2); } if ($this->hasMultipleSheets()) { // $id does not contain a sheet name while there are multiple sheets. Therefore, we // return an empty sheet name. This allows executing fallback handling in has() and get(). return ['', $id]; } // In case the $id does not contain a sheet name, but we have a // single sheet flex form, we fall back to this name automatically. return [key($this->sheets), $id]; } protected function resolveRecordPropertyClosure(string $id, mixed $propertyValue): mixed { if ($propertyValue instanceof RecordPropertyClosure) { try { $propertyValue = $propertyValue->instantiate(); } catch (\Exception $e) { // Consumers of this method can rely on catching ContainerExceptionInterface throw new FlexFieldPropertyException( 'An exception occurred while instantiating flex field property "' . $id . '"', 1731962735, $e ); } } return $propertyValue; } }