MrNorm
MrNorm

Reputation: 406

ZF2 - Creating custom form view helpers

Some time ago, Matthew Weier O'Phinney posted this article on his blog about creating composite form elements in Zend Framework 1.

I'm trying to create the same element for my custom library in Zend Framewor 2 but I'm having issues finding the form view helper when rendering the form.

Here is my element (DateSegmented.php):

<?php

namespace Si\Form\Element;

use Zend\Form\Element;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;

class DateSegmented extends Element implements ViewHelperProviderInterface
{

    public function getViewHelperConfig(){
          return array( 'type' => '\Si\Form\View\Helper\DateSegment' );
     }

    protected $_dateFormat = '%year%-%month%-%day%';
    protected $_day;
    protected $_month;
    protected $_year;

    /**
     * Seed attributes
     *
     * @var array
     */
    protected $attributes = array(
        'type' => 'datesegmented',
    );

    public function setDay($value)
    {
        $this->_day = (int) $value;
        return $this;
    }

    public function getDay()
    {
        return $this->_day;
    }

    public function setMonth($value)
    {
        $this->_month = (int) $value;
        return $this;
    }

    public function getMonth()
    {
        return $this->_month;
    }

    public function setYear($value)
    {
        $this->_year = (int) $value;
        return $this;
    }

    public function getYear()
    {
        return $this->_year;
    }

    public function setValue($value)
    {
        if (is_int($value)) {
            $this->setDay(date('d', $value))
                 ->setMonth(date('m', $value))
                 ->setYear(date('Y', $value));
        } elseif (is_string($value)) {
            $date = strtotime($value);
            $this->setDay(date('d', $date))
                 ->setMonth(date('m', $date))
                 ->setYear(date('Y', $date));
        } elseif (is_array($value)
            && (isset($value['day']) 
                && isset($value['month']) 
                && isset($value['year'])
            )
        ) {
            $this->setDay($value['day'])
                 ->setMonth($value['month'])
                 ->setYear($value['year']);
        } else {
            throw new Exception('Invalid date value provided');
        }

        return $this;
    }

    public function getValue()
    {
        return str_replace(
            array('%year%', '%month%', '%day%'),
            array($this->getYear(), $this->getMonth(), $this->getDay()),
            $this->_dateFormat
        );
    }
}

And here is my form view helper:

<?php

    namespace Si\Form\View\Helper;

    use Zend\Form\ElementInterface;
    use Si\Form\Element\DateSegmented as DateSegmented;
    use Zend\Form\Exception;

    class DateSegmented extends FormInput
    {
        /**
         * Render a form <input> element from the provided $element
         *
         * @param  ElementInterface $element
         * @throws Exception\InvalidArgumentException
         * @throws Exception\DomainException
         * @return string
         */
        public function render(ElementInterface $element)
        {
            $content = "";

            if (!$element instanceof DateSegmented) {
                throw new Exception\InvalidArgumentException(sprintf(
                    '%s requires that the element is of type Si\Form\Input\DateSegmented',
                    __METHOD__
                ));
            }

            $name = $element->getName();
            if (empty($name) && $name !== 0) {
                throw new Exception\DomainException(sprintf(
                    '%s requires that the element has an assigned name; none discovered',
                    __METHOD__
                ));
            }

            $view = $element->getView();
            if (!$view instanceof \Zend\View\View) {
                // using view helpers, so do nothing if no view present
                return $content;
            }

            $day   = $element->getDay();
            $month = $element->getMonth();
            $year  = $element->getYear();
            $name  = $element->getFullyQualifiedName();

            $params = array(
                'size'      => 2,
                'maxlength' => 2,
            );
            $yearParams = array(
                'size'      => 4,
                'maxlength' => 4,
            );

            $markup = $view->formText($name . '[day]', $day, $params)
                    . ' / ' . $view->formText($name . '[month]', $month, $params)
                    . ' / ' . $view->formText($name . '[year]', $year, $yearParams);

            switch ($this->getPlacement()) {
                case self::PREPEND:
                    return $markup . $this->getSeparator() . $content;
                case self::APPEND:
                default:
                    return $content . $this->getSeparator() . $markup;
            }

            $attributes            = $element->getAttributes();
            $attributes['name']    = $name;
            $attributes['type']    = $this->getInputType();
            $attributes['value']   = $element->getCheckedValue();
            $closingBracket        = $this->getInlineClosingBracket();

            if ($element->isChecked()) {
                $attributes['checked'] = 'checked';
            }

            $rendered = sprintf(
                '<input %s%s',
                $this->createAttributesString($attributes),
                $closingBracket
            );

            if ($element->useHiddenElement()) {
                $hiddenAttributes = array(
                    'name'  => $attributes['name'],
                    'value' => $element->getUncheckedValue(),
                );

                $rendered = sprintf(
                    '<input type="hidden" %s%s',
                    $this->createAttributesString($hiddenAttributes),
                    $closingBracket
                ) . $rendered;
            }

            return $rendered;
        }

        /**
         * Return input type
         *
         * @return string
         */
        protected function getInputType()
        {
            return 'datesegmented';
        }

    }

This question describes adding the view helper as an invokable, but it's already being declared, as my custom library (Si) has been added to the 'StandardAutoLoader'.

Upvotes: 3

Views: 4742

Answers (2)

MrNorm
MrNorm

Reputation: 406

OK, figured this one out eventually.

Copy Zend/Form/View/HelperConfig.php to the same location in your custom library. Adjust contents to reflect your view helpers.

Add the following to an event or bootstrap in your Module.php

$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
$phpRenderer = $serviceManager->get('ViewRenderer');

$plugins = $phpRenderer->getHelperPluginManager();
$config  = new \Si\Form\View\HelperConfig;
$config->configureServiceManager($plugins);

Update the 'Si' namespace with your custom one.

The 'class already exists' error was actually down to the includes at the top of my view helper file. I have updated it with:

use Zend\Form\View\Helper\FormElement;

use Zend\Form\Element;
use Zend\Form\ElementInterface;
use Zend\Form\Exception;

I also updated the instanceof statement to an absolute location due to the duplicate class names:

if (!$element instanceof \Si\Form\Element\DateSegmented) {

There were further errors in the translation from ZF1 to 2 but they are not related to this issue.

Upvotes: 1

Sam
Sam

Reputation: 16445

The way i understand your code is: You are creating a new Form\Element as well as a new Form\View\Helper. In this case the following information is needed for you:

The StandardAutoloader only takes care of actually finding the classes. The declaration of the invokables inside the getViewHelperConfig() is there, so the framework knows what Class to load when the ViewHelper is called.

In your case you'd do it like this:

public function getViewHelperConfig() 
{
    return array(
        'invokables' => array(
            'dateSegmented' => 'Si\Form\View\Helper\DateSegmented'
        )
    );
}

Zend Framework 2 does this for it's own ViewHelpers inside /Zend/Form/View/HelperConfig.php

Upvotes: 0

Related Questions