dadajuice/zephyrus

View on GitHub
src/Zephyrus/Application/Form.php

Summary

Maintainability
A
2 hrs
Test Coverage
A
100%
<?php namespace Zephyrus\Application;

use stdClass;

class Form
{
    private const SESSION_KEY = '__ZF_FORM_FIELDS';

    /**
     * List of all the submitted fields.
     *
     * @var FormField[]
     */
    private array $fields = [];

    /**
     * Contains all the triggered errors. Key are the name (with pathing if available) and value are an array or error
     * messages.
     *
     * @var array
     */
    private array $errors = [];

    /**
     * Reads a memorized value for a given field name. If value has not been set the specified default value is
     * assigned (empty if not set). Used mostly to set remembered data in forms. Field name can be given in the flatten
     * way for array.
     *
     * @param string $fieldName
     * @param mixed $defaultValue
     * @return mixed
     */
    public static function getSavedField(string $fieldName, mixed $defaultValue = ""): mixed
    {
        $savedFields = self::getSavedFields();
        return $savedFields[$fieldName] ?? $defaultValue;
    }

    public static function getSavedFields(): array
    {
        return self::flattenArray(self::getRawSavedFields());
    }

    public static function getRawSavedField(string $fieldName, mixed $defaultValue = ""): mixed
    {
        $savedFields = self::getRawSavedFields();
        return $savedFields[$fieldName] ?? $defaultValue;
    }

    public static function getRawSavedFields(): array
    {
        return $_SESSION["__ZF_FORM_FIELDS"] ?? [];
    }

    /**
     * Memorizes the specified value for the given fieldId. Allows to be read by the readMemorizedValue() function
     * afterward. Works with submitted array type values.
     *
     * @param string $fieldId
     * @param mixed $value
     */
    public static function memorizeValue(string $fieldId, mixed $value): void
    {
        if (!isset($_SESSION[self::SESSION_KEY])) {
            $_SESSION[self::SESSION_KEY] = [];
        }
        $_SESSION[self::SESSION_KEY][$fieldId] = $value;
    }

    /**
     * Removes the specified fieldId from memory or clears the entire memorized fields if not set.
     *
     * @param string|null $fieldId
     */
    public static function removeMemorizedValue(?string $fieldId = null): void
    {
        if (isset($_SESSION[self::SESSION_KEY])) {
            if (is_null($fieldId)) {
                $_SESSION[self::SESSION_KEY] = null;
                unset($_SESSION[self::SESSION_KEY]);
            } else {
                unset($_SESSION[self::SESSION_KEY][$fieldId]);
            }
        }
    }

    /**
     * Inserts a list of fields into the form data. formData is an associative array where the keys are the field name
     * and the value the corresponding values.
     *
     * @param array $formData
     */
    public function __construct(array $formData = [])
    {
        $this->addFields($formData);
    }

    /**
     * Retrieves a field from the form. If the field doesn't exist, a corresponding field will be added to the form data
     * with a NULL value. Useful to validate a required checkbox for example. If the rules are set, it will be applied
     * to the field before returning it.
     *
     * @param string $name
     * @param Rule[] $rules
     * @return FormField
     */
    public function field(string $name, array $rules = []): FormField
    {
        if (!isset($this->fields[$name])) {
            $this->addField($name, null);
        }
        return empty($rules) ? $this->fields[$name] : $this->fields[$name]->validate($rules);
    }

    /**
     * Validates each submitted fields with their configured rules. Accumulates errors.
     *
     * @return bool
     */
    public function verify(): bool
    {
        foreach ($this->fields as $field) {
            if (!$field->verify($this->getFields())) {
                $this->errors = $this->errors + $field->getErrors();
            }
        }
        return empty($this->errors);
    }

    /**
     * Inserts a list of fields into the form data. formData is an associative array where the keys are the field name
     * and the value the corresponding values.
     *
     * @param array $formData
     */
    public function addFields(array $formData): void
    {
        foreach ($formData as $parameterName => $value) {
            $this->addField($parameterName, $value);
        }
    }

    /**
     * Inserts a field into the form data.
     *
     * @param string $name
     * @param mixed $value
     */
    public function addField(string $name, mixed $value): void
    {
        $this->fields[$name] = new FormField($name, $value);
        self::memorizeValue($name, $value);
    }

    /**
     * Removes the specified field from the form data.
     *
     * @param string $name
     */
    public function removeField(string $name): void
    {
        if (isset($this->fields[$name])) {
            unset($this->fields[$name]);
            self::removeMemorizedValue($name);
        }
    }

    /**
     * Manually adds an error to the form for the specified field.
     *
     * @param string $name
     * @param string $message
     */
    public function addError(string $name, string $message): void
    {
        if (!isset($this->errors[$name])) {
            $this->errors[$name] = [];
        }
        $this->errors[$name][] = $message;
    }

    /**
     * Verifies if the form has error for a specified field or the entire form (if no field is provided).
     *
     * @param string|null $name
     * @return bool
     */
    public function hasError(?string $name = null): bool
    {
        return (is_null($name))
            ? !empty($this->errors)
            : isset($this->errors[$name]);
    }

    /**
     * Retrieves all the registered errors of the form in an associative array which the keys are the field name and the
     * values are an array of error messages. The keys may contain pathing for nested object errors.
     *
     * @return array
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    /**
     * Retrieves a simple array containing only the error messages (without field indications).
     *
     * @return array
     */
    public function getErrorMessages(): array
    {
        $messages = [];
        foreach ($this->errors as $errors) {
            foreach ($errors as $errorMessage) {
                $messages[] = $errorMessage;
            }
        }
        return $messages;
    }

    /**
     * Automatically prepares the feedbacks for the form fields with matching errors. The UI can then use the Feedback
     * class to properly display the field errors.
     */
    public function registerFeedback(): void
    {
        Feedback::register($this->getErrors());
    }

    /**
     * Automatically prepares the flash errors from the accumulated error messages.
     */
    public function registerFlash(): void
    {
        Flash::error($this->getErrorMessages());
    }

    /**
     * Tries to retrieve the value of the specified field name. If the field is not registered, the default value is
     * then returned.
     *
     * @param string $name
     * @param mixed $defaultValue
     * @return mixed|null
     */
    public function getValue(string $name, mixed $defaultValue = null): mixed
    {
        return isset($this->fields[$name]) ? $this->fields[$name]->getValue() : $defaultValue;
    }

    /**
     * Retrieves all form fields in a simple associative array with the keys being the submitted field names and the
     * value being the submitted raw value (e.g. ['test' => 3]).
     *
     * @return array
     */
    public function getFields(): array
    {
        $fields = [];
        foreach ($this->fields as $field) {
            $fields[$field->getName()] = $field->getValue();
        }
        return $fields;
    }

    /**
     * Verifies if a specific field is currently registered in the submitted form.
     *
     * @param string $field
     * @return bool
     */
    public function isRegistered(string $field): bool
    {
        return isset($this->fields[$field]);
    }

    /**
     * Returns an anonymous object with the fields as properties.
     *
     * @return stdClass
     */
    public function buildObject(): stdClass
    {
        $objectProperties = [];
        foreach ($this->getFields() as $field => $value) {
            $objectProperties[rtrim($field, "[]")] = $value;
        }
        return (object) $objectProperties;
    }

    private static function flattenArray(array $array, $prefix = ''): array
    {
        $results = [];
        foreach ($array as $key => $value) {
            $newKey = $prefix . (empty($prefix) ? '' : '[') . $key . (empty($prefix) ? '' : ']');
            if (is_array($value)) {
                $results = array_merge($results, self::flattenArray($value, $newKey));
            } else {
                $results[$newKey] = $value;
            }
        }
        return $results;
    }
}