nette / schema

📐 Validating data structures against a given Schema.
https://doc.nette.org/schema
Other
886 stars 26 forks source link

Allow recursive schema validation in tree structure #40

Open foxycode opened 3 years ago

foxycode commented 3 years ago

I'm trying to solve recursive schema validation in menu library when you have tree structure of menu (submenu items).

Here's what I did as temporary solution:

final class MenuExtension extends CompilerExtension
{

    public function getConfigSchema(): Schema
    {
        return Expect::arrayOf(Expect::structure([
            'loader' => Expect::string(DefaultMenuLoader::class),
            'items' => Expect::array()->required(),
        ]));
    }

    public function getItemSchema(): Schema
    {
        return Expect::structure([
            'title' => Expect::string(),
            'link' => Expect::string(),
            'items' => Expect::array(),
        ]);
    }

    public function loadConfiguration(): void
    {
        $config = $this->getConfig();
        $builder = $this->getContainerBuilder();
        $processor = new Processor;

        foreach ($config as $menuName => $menu) {
            $container->addSetup('addMenu', [
                $this->loadMenuConfiguration($builder, $processor, $menuName, $menu),
            ]);
        }
    }

    private function loadMenuConfiguration(
        ContainerBuilder $builder,
        Processor $processor,
        string $menuName,
        stdClass $config
    ): ServiceDefinition {
        $loader = $config->loader;

        if (!Strings::startsWith($config->loader, '@')) {
            $loader = $builder->addDefinition($this->prefix('menu.'. $menuName. '.loader'))
                ->setType($config->loader)
                ->setAutowired(false);
        }

        if ($loader->getType() === DefaultMenuLoader::class) {
            $loader->setArguments([$this->normalizeMenuItems($processor, $config->items)]);
        }
    }

    private function normalizeMenuItems(Processor $processor, array $items): array
    {
        array_walk($items, function(array &$item, string $key) use ($processor): void {
            $item = $processor->process($this->getItemSchema(), $item);

            if ($item->title === null) {
                $item->title = $key;
            }

            $item->items = $this->normalizeMenuItems($processor, $item->items);
        });

        return $items;
    }

}

Full file is here