Skip to content

[Twig Component] Feature: add possibility to validate all incoming data to component #142

@sabat24

Description

@sabat24

I want to validate all data passed from twig into my php component. Right now I can do such thing only by creating a setter for each property. Eventually use mount method and explicitly define there each property as an argument.

What I need is more generic method to validate such data. I was thinking about 2 solutions.

  1. Built-in method like mount named configureProperties
  2. A callback to some method inside my component.

As I prefer the second solution I wrote some code to show you the case using the first one (was easier to me).

//ComponentFactory.php

public function create(string $name, array $data = []): object
    {
        $component = $this->getComponent($name);

        $this->mount($component, $data);
        $this->configureProperties($component, $data); // <- add this line

        foreach ($data as $property => $value) {
            if (!$this->propertyAccessor->isWritable($component, $property)) {
                throw new \LogicException(sprintf('Unable to write "%s" to component "%s". Make sure this is a writable property or create a mount() with a $%s argument.', $property, \get_class($component), $property));
            }

            $this->propertyAccessor->setValue($component, $property, $value);
        }

        return $component;
    }

// add these methods

private function configureProperties(object $component, array &$data): void
    {
        try {
            $method = (new \ReflectionClass($component))->getMethod('configureProperties');
        } catch (\ReflectionException $e) {
            // no hydrate method
            return;
        }
        $data = array_merge($data, $this->getComponentPropertiesDefaultValues($component));
        $optionsResolver = new OptionsResolver();
        $component->configureProperties($optionsResolver);
        $data = $optionsResolver->resolve($data);
    }

    private function getComponentPropertiesDefaultValues(object $component): array
    {
        $reflection = new \ReflectionClass($component);
        $propertiesReflections = array_filter(
            $reflection->getProperties(\ReflectionProperty::IS_PUBLIC),
            static fn($property): bool => !$property->isStatic() && $property->getDefaultValue() !== null,
        );

        $properties = [];

        array_map(static function ($property) use (&$properties) {
            $properties[$property->name] = $property->getDefaultValue();
        }, $propertiesReflections);

        return $properties;
    }
// Components/CustomComponent.php

final class CustomComponent
{
    public string $name;

    public string $size = 'm';

    public string $align;

    public function configureProperties(OptionsResolver $optionsResolver)
    {
        $optionsResolver
            ->setDefined(['size'])
            ->setDefaults(['align' => 'center'])
            ->setAllowedValues('size', ['xs', 's', 'm', 'l', 'xl',])
            ->setAllowedValues('align', ['left', 'center', 'right'])
            ->setRequired(['name']);

    }
#components/custom_component.html.twig
<div class="size-{{ this.size }} align-{{ this.align }}">
    <h1>{{ this.name }}</h1>
    <p>Size: {{ this.size }}</p>
    <p>Align: {{ this.align }}</p>
</div>
{{ component('custom_component', {name: 'some name'}) }}

configureProperties method in ComponentFactory is responsible for setting default values defined in component's class properties into $data variable. Then it tries to resolve provided options using my custom components rules and allows TwigComponent to set validated data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions