formConstructorProperties = array_map(fn($e) => strtolower($e->getName()), array_filter($reflection->getConstructor()->getParameters(), fn($e) => $e->isPromoted())); $this->formContextValues = $formContextRequest->getParsedBody() ?: []; if ( ! $this->formContextValues ) { $content = mb_convert_encoding((string) $formContextRequest->getBody(), 'UTF-8'); if ( $content && ( $json = json_decode($content, true) ) ) { $this->formContextValues = $json; } } if ($this->formContextValuesRoot ?? false) { $this->formContextValues = $this->formContextValues[$this->formContextValuesRoot] ?? []; } $this->fillValues(); $this->formContextFiles = $formContextRequest->getUploadedFiles() ?: []; $this->initialize(); } public function initialize() : void {} public function valid() : bool { foreach($this->formContextMessages as $message) { if ( $message->isError() ) { return false; } } return true; } public function executed() : bool { return $this->formExecutionStatus === false ? false : $this->formExecuted; } public function formSent() : bool { $valid = in_array($this->requestMethod(), $this->formContextCatchedMethods); if ( (! $this->formSkipCsrf) && ! empty($this->formName) ) { $token = $this->get('picea-ui-form')[$this->formName] ?? false; if ( $token ) { if (! $this->formSkipCsrf) { $valid = in_array($token, $_SESSION["picea-ui.form:{$this->formName}"] ?? []); } else { $valid = (bool) $token; } } else { $valid = false; } } return $this->formSent = $valid; } public function getExecutionStatus() : mixed { return $this->formExecutionStatus; } public function getRequest() : ServerRequestInterface { return $this->formContextRequest; } public function requestMethod() : string { return $this->formContextRequest->getMethod(); } public function __set($key, $value) { return $this->set($key, $value); } public function __get($key) { return $this->get($key); } public function __isset($key) { return array_key_exists($key, $this->formContextValues); } public function __unset($key) { $this->delete($key); } public function get(string $key, mixed $default = null) { return $this->has($key) ? $this->formContextValues[$key] : $default; } public function set(string $key, mixed $value) { if ($this->canWriteProperty($key)) { $this->$key = $this->castValue($key, $value); } return $this->formContextValues[$key] = $value; } public function sets(array $values) : static { foreach($values as $key =>$value) { $this->set($key, $value); } return $this; } public function delete(string $key) : void { unset($this->formContextValues[$key]); } public function has(string $key) : bool { return array_key_exists($key, $this->formContextValues); } public function pushMessage(FormMessage $message) : void { if ($message->isError() /* && $message->isSuccess() @TODO next major v. */ ) { $this->formContextMessages = array_merge([ $message ], $this->formContextMessages); } else { $this->formContextMessages[] = $message; } } public function messages() : array { return $this->formContextMessages; } protected function fillValues() : void { foreach($this->formContextValues as $property => $value) { if ($this->canWriteProperty($property)) { $this->$property = $this->castValue($property, $value); $this->formContextDefinedProperties[$property] = true; } } } protected function canWriteProperty(string $property) : bool { # Skipping overrides of this particular class vars as a security measure static $skipping = array_keys(array_change_key_case(get_class_vars(FormContext::class), CASE_LOWER)); return ! in_array(strtolower($property), array_merge($skipping, $this->formConstructorProperties)) && property_exists($this, $property); } protected function definedProperty(string $property) : bool { return $this->formContextDefinedProperties[$property] ?? false; } protected function castValue(string $property, mixed $value) : mixed { $cast = fn($e) => match(true) { is_a($e, Context\FormContextCastableInterface::class, true) => $e::instance($value), $e === "array" => (function(array|string $value) use ($property) { if (is_string($value)) { return json_validate($value) ? json_decode($value, true) ?? [] : throw new \InvalidArgumentException("Field '$property' is awaiting JSON encoded content."); } return $value; })($value), is_null($value) => null, enum_exists($e) => ( $value instanceof \BackedEnum ? $value : $e::tryFrom($value) ) ?? throw new \InvalidArgumentException(sprintf("Field '\$$property' awaiting values : '%s'.", implode("', '", array_map(fn($e) => $e->value, $e::cases())))), function_exists($e) => $e($value), class_exists($e) => new $e(...$value), interface_exists($e) => throw new \InvalidArgumentException("There is no way provided to match an interface object yet."), default => $value }; $types = ( new \ReflectionProperty($this, $property) )->getType(); if ($types instanceof \ReflectionNamedType) { $value = $cast($types->getName()); } elseif ($types instanceof \ReflectionIntersectionType || $types instanceof \ReflectionUnionType) { foreach($types->getTypes() as $type) { return $cast($type->getName()); } } elseif ($types->isBuiltin()) { return $cast($types->getName()); } return $value; } }