src/Form/AbstractLayout.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Atk4\Ui\Form;

use Atk4\Core\WarnDynamicPropertyTrait;
use Atk4\Data\Field;
use Atk4\Data\Model;
use Atk4\Ui\Button;
use Atk4\Ui\Exception;
use Atk4\Ui\Form;
use Atk4\Ui\Misc\ProxyModel;
use Atk4\Ui\View;

/**
 * Custom Layout for a form.
 *
 * @property false $model use $entity property instead
 */
abstract class AbstractLayout extends View
{
    use WarnDynamicPropertyTrait;

    /** Links layout to owner Form. */
    public Form $form;

    protected function _addControl(Control $control, Field $field): Control
    {
        return $this->add($control, $this->template->hasTag($field->shortName) ? $field->shortName : null);
    }

    /**
     * Places element inside a layout somewhere. Should be called
     * through $form->addControl().
     *
     * @param array<mixed>|Control $control
     * @param array<mixed>         $fieldSeed
     */
    public function addControl(string $name, $control = [], array $fieldSeed = []): Control
    {
        if ($this->form->entity === null) {
            $this->form->setModel((new ProxyModel())->createEntity());
        }
        $model = $this->form->entity->getModel();

        // TODO this class should not refer to any specific form control
        $controlClass = is_object($control)
            ? get_class($control)
            : ($control[0] ?? (($fieldSeed['ui'] ?? [])['form'][0] ?? null));
        if (is_a($controlClass, Control\Checkbox::class, true)) {
            $fieldSeed['type'] = 'boolean';
        } elseif (is_a($controlClass, Control\Dropdown::class, true) || is_a($controlClass, Control\Lookup::class, true)) {
            if (is_a($controlClass, Control\DropdownCascade::class, true)) {
                $cascadeFromControl = $control instanceof Control\DropdownCascade ? $control->cascadeFrom : ($control['cascadeFrom'] ?? null);
                if ($cascadeFromControl !== null) {
                    if (!$cascadeFromControl instanceof Control) {
                        $cascadeFromControl = $this->form->getControl($cascadeFromControl);
                    }

                    $fieldSeed['type'] = $cascadeFromControl->entityField->getField()->type;
                }
            } else {
                $dropdownModel = $control instanceof Control ? $control->model : ($control['model'] ?? null);
                if ($dropdownModel !== null) {
                    $fieldSeed['type'] = $dropdownModel->getIdField()->type;
                }
            }
        } elseif (is_a($controlClass, Control\Calendar::class, true)) {
            $calendarType = $control instanceof Control\Calendar ? $control->type : ($control['type'] ?? null);
            if ($calendarType !== null) {
                $fieldSeed['type'] = $calendarType;
            }
        }

        try {
            if ($model->hasField($name)) {
                $field = $model->getField($name)->setDefaults($fieldSeed); // TODO assert same defaults only
            } else {
                $field = $model->addField($name, $fieldSeed);
            }

            $control = $this->form->controlFactory($field, $control);
        } catch (\Exception $e) {
            if ($e instanceof \ErrorException) {
                throw $e;
            }

            throw (new Exception('Unable to create form control', 0, $e))
                ->addMoreInfo('name', $name)
                ->addMoreInfo('control' . (!is_object($control) ? 'Seed' : ''), $control)
                ->addMoreInfo('fieldSeed', $fieldSeed);
        }

        return $this->_addControl($control, $field);
    }

    /**
     * Returns array of names of fields to automatically include them in form.
     * This includes all editable or visible fields of the model.
     *
     * @return list<string>
     */
    protected function getModelFields(Model $model)
    {
        return array_keys($model->getFields('editable'));
    }

    /**
     * Sets form model and adds form controls.
     *
     * @param list<string>|null $fields
     */
    #[\Override]
    public function setModel(Model $entity, ?array $fields = null): void
    {
        $entity->assertIsEntity();

        parent::setModel($entity);

        if ($fields === null) {
            $fields = $this->getModelFields($entity);
        }

        // add controls - check if fields are editable or read-only/disabled
        foreach ($fields as $fieldName) {
            $field = $entity->getField($fieldName);

            $controlSeed = null;
            if ($field->isEditable()) {
                $controlSeed = [];
            } elseif ($field->isVisible()) {
                $controlSeed = ['readOnly' => true];
            }

            if ($controlSeed !== null) {
                $this->addControl($field->shortName, $controlSeed);
            }
        }
    }

    /**
     * Return Field decorator associated with the form's field.
     */
    public function getControl(string $name): Control
    {
        return $this->form->getControl($name);
    }

    /**
     * Adds Button into form layout.
     *
     * @param Button|array<mixed> $seed
     *
     * @return Button
     */
    abstract public function addButton($seed);
}