TUTORIAL - Form elements with multidimensional array easy peasy way :)

Array helper class for element tag name

<?php

class Arr {

    /**
     * Convert string a[b][c] TO  a.b.c
     * @param type $string
     * @return type
     */
    public static function stringToDotString($string) {
        return trim(preg_replace('/[\[\]]+/', '.', $string), '.');
    }

    /**
     *  Convert array a[b][c] TO  a.b.c
     * @param type $array
     * @param type $prefix
     * @return array
     */
    public static function arrayToPath(array $array, $prefix = '') {
        $result = array();

        foreach ($array as $key => $value) {
            $new_key = $prefix . (empty($prefix) ? '' : '.') . $key;

            if (is_array($value)) {
                $result = array_merge($result, self::arrayToPath($value, $new_key));
            } else {
                $result[$new_key] = $value;
            }
        }

        return $result;
    }

    /**
     * a.b.c convert TO a[b][c]
     * @param string $string
     * @param mixed $value
     * @param string $separator
     * @return array
     */
    static function stringPathToArray($string, $value = '', $separator = '.') {
        $keys = array_reverse(explode($separator, $string));
        $tmp = $value;
        foreach ($keys as $key) {
            $tmp = [$key => $tmp];
        }
        return $tmp;
    }

    /**
     * Convert Array a.b.c to a[b][c]
     * @param array $array
     * @param string $separator
     * @return array
     */
    public static function pathToArray(array $array, $separator = '.') {
        $result = [];
        foreach ($array as $string => $value) {
            $single = self::stringPathToArray($string, $value, $separator);
            $result = array_merge_recursive($result, $single);
        }
        return $result;
    }

    public static function arrayToHtmlTagNames($array) {
        $result = array();
        $iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array));
        foreach ($iterator as $value) {
            $dotString = '';
            for ($i = 0; $i <= $iterator->getDepth(); $i++) {
                if ($i == 0) {
                    $dotString .= $iterator->getSubIterator($i)->key();
                } else {
                    $dotString .= "[" . $iterator->getSubIterator($i)->key() . "]";
                }
            }
            $result[$dotString] = $value;
        }
        return $result;
    }

    /**
     * a.b.c TO a[b][c]
     * @param type $string
     * @param type $separator
     * @return type
     */
    public static function stringToHtmlTagName($string, $separator = '.') {
        $result = static::stringPathToArray($string, '', $separator);

        $array = self::arrayToHtmlTagNames($result);
        return array_keys($array)[0];
    }

    public static function array_values_recursive(array $array) {
        $result = array();
        foreach (array_keys($array) as $key) {
            $value = $array[$key];
            if (is_scalar($value)) {
                $result[] = $value;
            } elseif (is_array($value)) {
                $result = array_merge($result, self::array_values_recursive($value));
            }
        }
        return $result;
    }

}

Extending form

<?php

use Arr;
use Phalcon\Forms\Element\AbstractElement;

class CustomForm extends \Phalcon\Forms\Form {

    /**
     * POST values to form
     * @param array $data
     */
    public function setDefaults(array $data) {
        foreach ($data as $name => $value) {
            if ($this->has($name)) {
                $this->get($name)->setDefault($value); //for single element
            } elseif (is_string($value) && $this->has($name . $value)) {
                $this->get($name . $value)->setDefault($value); //for single radio group element
            } else {

                $keysValues = Arr::arrayToPath([$name => $value]);
                foreach ($keysValues as $k => $v) {
                    if ($this->has($k)) {
                        $this->get($k)->setDefault($v);
                    } else {
                        $forMultipleSelectKey = join('.', explode('.', $k, -1)); //for  multiple values, SELECT[] or CHECK[]
                        if ($this->has($k . $v)) {
                            $this->get($k . $v)->setDefault($v);
                        } else if ($this->has($forMultipleSelectKey)) {
                            $this->get($forMultipleSelectKey)->setDefault(Arr::array_values_recursive($value));
                        }
                    }
                }
            }
        }
    }

    /**
     * Render HTML element
     * @param AbstractElement $element
     * @return AbstractElement
     */
    public function renderElement(AbstractElement $element) {
        $elementID = $this->formatElementId($element);
        $this->formatElementHtmlName($element);
        $str = '';
        $str .= '<label for="' . $elementID . '">' . $element->getLabel() . '</label>';
        $str .= $element;
        return $str;
    }

    public function formatElementId(AbstractElement $element) {
        $id = $element->getAttribute('id', false);
        if ($id === false) {
            $element->setAttribute('id', str_replace('.', '_', $element->getName()));
        }
        return $element->getAttribute('id');
    }

    public function formatElementHtmlName(AbstractElement $element) {

        $name = $element->getAttribute('name', $element->getName());
        $nameAttribute = Arr::stringToHtmlTagName($name);

        if ($element instanceof \Phalcon\Forms\Element\Select) {
            $options = $element->getOptions();
            if (isset($options['multiple'])) {
                $nameAttribute .= '[]';
            }
        }
        $element->setAttribute('name', $nameAttribute);
    }
}

And yours custom form

<?php

use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Select;
use App\Models\Food\CategoryModel;// use yours model
use Phalcon\Forms\Element\Check;
use Phalcon\Forms\Element\Radio;

class DummyForm extends CustomForm {

    public function initialize() {

        $title = new Text('a.d.f.e.f.sa.dd.f.g.h.h.e.e.e');
        $title->setLabel('title');

        $category = new Select('c.a.t.e.g.o.r.i.e.s.a.b.c', CategoryModel::find(), [
            'emptyText' => 'Please Select...',
            'emptyValue' => '',
            'useEmpty' => true,
            'using' => [
                'id',
                'title',
            ],
            'multiple' => true,
        ]);
        $category->setLabel('Multiple categories');

        $this->add($title);
        $this->add($category);
        $checks = ['a', 'b', 'c'];

        foreach ($checks as $key) {
            $this->add(new Check('a.s.f.g.h.j.check.' . $key, ['value' => $key]));
        }

        foreach ($checks as $key) {
            $elementId = 'sd.dg.h.j.d.d.s.s.a.b.x.radio-elements';
            $this->add(new Radio($elementId . $key, ['value' => $key, 'name' => $elementId]));
        }
    }

}
<?php
class IndexController extends \Phalcon\Mvc\Controller {

    public function indexAction() {

        $form = new DummyForm();
        if ($this->request->isPost()) {

            $form->setDefaults($_POST);
        }
        $this->view->form = $form;
    }
}

AND VIEW

<?php
        foreach($form->getElements() as $element){
            $form->renderElement($element); //important to use renderElement method
        }

Thanks "_