Working with complex decorators in Zend Framework

Introduction


Zend Framework — a wonderful system. This opinion I have formed on the long closer "communication" with this system. And it is remarkable not because of any special abilities provided by the programmer, and due to the fact that this system is an amazing way invites the programmer to own the improvement for it, the programmer, the benefit of offering simple and at the same time a strong Foundation for your own development.
While working on a project using Zend Framework, I decided to try to make maximum use of its capabilities, and immediately drew attention to the Zend_Form component (I deliberately call Zend_Form component, not a class, because Zend_Form component consists of class Zend_Form and a set of related classes and interfaces). In the documentation it is written quite simply: "Zend_Form simplifies form creation and managing your web applications." In General this is true, but without preparation you sweat it to the Oldies before you will be able to create and render one more or less complex form. Conceptual form in Zend Framework consists of:
    the
  • elements
  • the
  • decorators
  • the
  • filters
  • the
  • validators
Elements is, in fact, what we mean by form elements: input fields, drop-down lists, etc.
Decorator — it is the whole layout, which is logically connected with the form element (surrounds it), but is not part of it. Simply put, decorator — design of a form element.

In this article, I will prove the necessity of existence of group of designer, and publish their code. Too lazy to read the fruit of getmanstva the author can directly go to the code.
the

So what's the problem?


Work with decorators so far, in my opinion, the most difficult and laborious part of the work on the implementation of the forms. In the examples in the documentation, everything is great, except for one thing — it's all very simple, I would even say primitive examples.
But as soon as you want to display something like this

then the problems begin. The main problem is that the repository decorators in Zend_Form component has a one-tier structure, that is, you cannot create nested decorators, you cannot create a decorator for the decorator, etc.
For example, the above portion of the form is encoded with the following code:

As we can see in the element tree, there are two independent branches: a cell of a table frame directly to the form element (input) and the cell frame label (label). The trouble is that the label is not a form element (it is also a decorator). Therefore, in order to create such a structure needs to get right with dirty hacks:
    the
  1. $this->setElementDecorators(array(
  2. 'ViewHelper'

    array('decorator' => array('br' => 'HtmlTag') 'options' => array('tag' => 'span' 'placement' = > Zend_Form_Decorator_Abstract::APPEND)),

    array('decorator' => array('tdOpen' => 'HtmlTag') 'options' => array('tag' => 'td' 'openOnly' => true 'placement' = > Zend_Form_Decorator_Abstract::PREPEND)),

    array('decorator' => array('tdClose' => 'HtmlTag') 'options' => array('tag' => 'td' 'closeOnly' => true 'placement' = > Zend_Form_Decorator_Abstract::APPEND)),

    array('decorator' => array('label' => 'Label') 'options' => array('separator' => '*')),

    'Errors'

    array('decorator' => array('mainCell' => 'HtmlTag') 'options' => array('tag' => 'td' 'class' => 'tregleft')), the

  3. ));
  4. the

  5. $usernameFieldLabel = $this->user_name- > getDecorator('label');
  6. the
  7. $defaultSeparator = $usernameFieldLabel->getOption('separator');
  8. the
  9. $usernameFieldLabel->setOption('separator', $defaultSeparator.'
    Login can contain only Latin letters and underscore "_".'
    );
* This source code was highlighted with Source Code Highlighter.

Among the shortcomings:
    the
  • manually inlining the closing and opening td tags
  • the
  • using the options separator not on purpose
and just, not every code, even using the hacks, you will be able so to display using decorators. It would have been much easier if there was a decorator that I can add other decorators.
the

the Solution


Googling, I do not normally have found and decided to write your own decorator group.
Here's what happened:

    <?php the

  1. require_once 'Zend/Form/Decorator/Abstract.php';
  2. class Zend_Form_Decorator_GroupDecorator extends Zend_Form_Decorator_Abstract { the

  3. /**
  4. * Items (decorators) to group

    * @var array the

  5. */
  6. protected $_items = null;

    the

  7. /**
  8. * Temporary form decorators to perform operations

    * @var Zend_Form_Element the

  9. */
  10. private $_temporaryDecoratorsContainer = null;

    the

  11. /**
  12. * Constructor

    *

    * @param array|Zend_Config $options

    * @return void the

  13. */
  14. public function __construct($options = null) { the

  15. parent::__construct($options);
  16. the
  17. $this->_temporaryDecoratorsContainer = new Zend_Form_Element('_temporaryDecoratorsContainer', array('DisableLoadDefaultDecorators' => true));
  18. the
  19. $this->getItems();
  20. the
  21. }
  22. the

  23. /**
  24. * Set items to use

    *

    * @param array $items

    * @return Zend_Form_Decorator_GroupDecorator the

  25. */
  26. public function setItems($items) { the

  27. $this->_items = $this->_temporaryDecoratorsContainer->clearDecorators()->addDecorators($items)->getDecorators();
  28. return $this; the

  29. }
  30. the

  31. /**
  32. * Get the tag

    *

    * If no items is registered, either via setItems() or as an option, uses empty array.

    *

    * @return array the

  33. */
  34. public function getItems() {

    if (null === $this->_items) {

    if (null === ($items = $this->getOption('items'))) { the

  35. $this->setItems(array());
  36. the
  37. } else {
  38. the
  39. $this->setItems($items);
  40. the
  41. $this->removeOption('items');
  42. the
  43. }
  44. the
  45. }
  46. return $this->_items; the

  47. }
  48. public function addDecorator($decorator, $options = null) { the

  49. $this->_temporaryDecoratorsContainer- > addDecorator($decorator, $options);
  50. return $this; the

  51. }
  52. public function clearDecorators() { the

  53. $this->_temporaryDecoratorsContainer- > clearDecorators();
  54. the
  55. $this->_items = array();
  56. the
  57. }
  58. public function getDecorator($index = null) {

    if (null === $index) {

    return $this->_items;

    if (is_numeric($index)) { the

  59. $_items = array_values($this->_items);
  60. return ($index < count($_items))?$_items[$index]:null; the

  61. }
  62. if (is_string($index)) {

    return (array_key_exists($index, $this->_items))?$this->_items[$index]:null; the

  63. }
  64. return null; the

  65. }
  66. public insertDecoratorBefore function($index, $decorator, $options = null) { the

  67. $_decoratorsToAdd = $this->_temporaryDecoratorsContainer->clearDecorators()->addDecorator($decorator, $options)- > getDecorators();
  68. if (is_string($index)) { the

  69. $index = array_search($index, array_keys($this->_items));
  70. the
  71. }
  72. if (false !== $index) { the

  73. $first = ($index > 0)?array_slice($this->_items, 0, $index, true):array();
  74. the
  75. $last = ($index < count($this->_items))?array_slice($this->_items, $index, null true):array();
  76. the
  77. $this->_items = array_merge($first, (array)$_decoratorsToAdd, $last);
  78. the
  79. }
  80. return $this; the

  81. }
  82. the

  83. /**
  84. * Render content wrapped in a group of decorators

    *

    * @param string $content

    * @return string the

  85. */
  86. public function render($content) { the

  87. $placement = $this->getPlacement();
  88. the
  89. $items = $this->getItems();
  90. the
  91. $_content = ";
  92. foreach ($items as $_decorator) {

    if ($_decorator instanceOf Zend_Form_Decorator_Interface) { the

  93. $_decorator- > setElement($this->getElement());
  94. the
  95. $_content = $_decorator->render($_content);
  96. the
  97. }
  98. else { the

  99. require_once 'Zend/Form/Decorator/Exception.php';
  100. throw new Zend_Form_Decorator_Exception('Invalid decorator '.$_decorator.' provided; must be string or Zend_Form_Decorator_Interface'); the

  101. }
  102. the
  103. }
  104. switch ($placement) {

    case self::APPEND:

    return $content . $_content;

    break;

    case self::PREPEND:

    return $_content . $content;

    break;

    default:

    return $_content.$content.$_content;

    break; the

  105. }
  106. the
  107. }
  108. the
  109. }
  110. the
  111. ?>
* This source code was highlighted with Source Code Highlighter.

This decorator can:
    the
  • to Create and display decorators arbitrary nesting, and this means that whatever the complexity
  • the
  • to perform operations on the decorators (add, delete, insert) on the fly, thereby allowing you to edit group set default
Now the above example of creating a form element will look like this:

    <?php

    class Form_MemberRegister extends Zend_Form {

    public function init() { the

  1. $this->setDisableLoadDefaultDecorators(true);
  2. the

  3. $this->addDecorator('FormElements')
  4. the
  5. - > addDecorator(array('table' => 'HtmlTag'), array('tag' => 'table' 'class' => 'treg'))
  6. the
  7. - > addDecorator('Form');
  8. the

  9. $this->addElement('text' 'user_name', array('label' => 'Login'));
  10. the
  11. $this->addElement('password' 'password2', array('label' => 'Repeat password:'));
  12. the
  13. $this->addElement('text' 'email', array('label' => 'E-mail'));
  14. the

  15. $this->setElementDecorators(array(
  16. array('decorator' => array('labelGroup' => 'GroupDecorator') 'options' => array('items' => array(

    array('decorator' => 'Text' 'options' => array('text' => '*')),

    array('decorator' => array('span' => 'HtmlTag') 'options' => array('tag' => 'span' 'class' => 'red')),

    array('decorator' => 'Label''options' => array('placement' = > Zend_Form_Decorator_Abstract::PREPEND)),

    array('decorator' => array('labelCell' => 'HtmlTag') 'options' => array('tag' => 'td' 'class' => 'tregleft')) the

  17. ))),
  18. array('decorator' => array('elementGroup' => 'GroupDecorator') 'options' => array('items' => array(

    'ViewHelper'

    array('decorator' => array('elementCell' => 'HtmlTag') 'options' => array('tag' => 'td')) the

  19. )) 'placement' = > Zend_Form_Decorator_Abstract::APPEND),
  20. array('decorator' => array('mainRowClose' => 'HtmlTag') 'options' => array('tag' => 'tr')) the

  21. ));
  22. the

  23. /**
  24. * @var Zend_Form_Decorator_GroupDecorator the

  25. */
  26. the
  27. $usernameFieldLabel = $this->user_name- > getDecorator('labelGroup');
  28. the
  29. $usernameFieldLabel->insertDecoratorBefore('labelCell', array('usernameNotes' => $this->_getNotesDecorator($this->user_name, 'the Username can only consist of Latin letters and underscore "_".')));
  30. the

  31. /**
  32. * @var Zend_Form_Decorator_GroupDecorator the

  33. */
  34. the
  35. $emailFieldDecorator = $this->email->getDecorator('elementGroup');
  36. the
  37. $emailFieldDecorator->insertDecoratorBefore('elementCell', array('emailNotes' => $this->_getNotesDecorator($this->email, 'Enter only existing and working e-mail. At this address You will be sent a link to confirm your registration.')));
  38. the
  39. }
  40. protected _getNotesDecorator function($element, $notesText = ") { the

  41. $_d = new Zend_Form_Decorator_GroupDecorator(array('items' => array(
  42. array('decorator' => array('notesText' => 'Text') 'options' => array('text' => $notesText)),

    array('decorator' => array('notesTag' => 'HtmlTag') 'options' => array('tag' => 'small')),

    array(

    'decorator' => array('br' => 'HtmlTag'),

    'options' => array('tag' => 'br' 'openOnly' => true 'placement' = > Zend_Form_Decorator_Abstract::PREPEND) the

  43. )
  44. the
  45. )));
  46. return $_d- > setElement($element); the

  47. }
  48. the
  49. }
  50. the
  51. ?>
* This source code was highlighted with Source Code Highlighter.

If there is another, more elegant solution, of the problem, let me know — I will sprinkle his ashes on his head :)
PS: For opponents of table layout I want to note that this is a post about programming, I'm not a coder — I just had to map a given pattern.
Article based on information from habrahabr.ru

Comments

Popular posts from this blog

Powershell and Cyrillic in the console (updated)

Active/Passive PostgreSQL Cluster, using Pacemaker, Corosync

Automatic deployment ElasticBeanstalk using Bitbucket Pipelines