这是正在进行的关于Zend_Form
装饰器系列的第二篇。
在上一期中,您可能已经注意到装饰器的render()
方法采用单个参数$content
。这应该是一个字符串。render()
然后将获取此字符串并决定替换它、追加它或添加它。这允许您拥有装饰器链——这允许您创建仅呈现元素元数据子集的装饰器,然后将这些装饰器分层以构建元素的完整标记。
让我们看看它在实践中是如何工作的。
对于大多数表单元素类型,使用以下装饰器:
-
ViewHelper
(使用标准表单视图助手之一呈现表单输入) -
错误
(通过无序列表呈现验证错误) -
Description
(呈现附加到元素的任何描述;通常用于工具提示) -
HtmlTag
(将以上所有内容包装在- 标签中
-
Label
(渲染上面的标签,包裹在- 标签中
您会注意到这些装饰器中的每一个都只做一件事,并且对存储在表单元素中的一段特定元数据进行操作:“错误”装饰器提取验证错误并呈现它们;“标签”装饰器只拉出标签并渲染它。这使得各个装饰器非常简洁、可重复,更重要的是,可测试。
这也是$content
参数发挥作用的地方:每个装饰器的render()
方法被设计为接受内容,然后替换它(通常通过包装它)、添加到它之前或附加到它。
因此,最好将装饰过程想象成从内到外构建洋葱的过程。
为了简化流程,我们将看一下本系列上一篇文章中的示例。召回:
class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract { protected $_format = '<label for="%s">%s</label><input id="%s" name="%s" type="text" value="%s"/>'; public function render($content) { $element = $this->getElement(); $name = htmlentities($element->getFullyQualifiedName()); $label = htmlentities($element->getLabel()); $id = htmlentities($element->getId()); $value = htmlentities($element->getValue()); $markup = sprintf($this->_format, $id, $label, $id, $name, $value); return $markup; } }
现在让我们删除标签功能,并为此构建一个单独的装饰器。
class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract { protected $_format = '<input id="%s" name="%s" type="text" value="%s"/>'; public function render($content) { $element = $this->getElement(); $name = htmlentities($element->getFullyQualifiedName()); $id = htmlentities($element->getId()); $value = htmlentities($element->getValue()); $markup = sprintf($this->_format, $id, $name, $value); return $markup; } } class My_Decorator_SimpleLabel extends Zend_Form_Decorator_Abstract { protected $_format = '<label for="%s">%s</label>'; public function render($content) { $element = $this->getElement(); $id = htmlentities($element->getId()); $label = htmlentities($element->getLabel()); $markup = sprintf($this->_format, $id, $label); return $markup; } }
现在,这看起来一切都很好,但问题是:正如目前所写的那样,最后运行的装饰器获胜,并覆盖所有内容。您最终只会得到输入或标签,具体取决于您最后注册的内容。
为了克服这个问题,只需将传入的$content
与标记以某种方式连接起来即可:
return $content . $markup;
当您想要以编程方式选择原始内容是应该在新标记之前还是附加到新标记时,上述方法就会出现问题。幸运的是,已经有了一个标准的机制;Zend_Form_Decorator_Abstract
有一个放置的概念并定义了一些常量来匹配它。此外,它允许指定一个分隔符放置在两者之间。让我们利用这些:
class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract { protected $_format = '<input id="%s" name="%s" type="text" value="%s"/>'; public function render($content) { $element = $this->getElement(); $name = htmlentities($element->getFullyQualifiedName()); $id = htmlentities($element->getId()); $value = htmlentities($element->getValue()); $markup = sprintf($this->_format, $id, $name, $value); $placement = $this->getPlacement(); $separator = $this->getSeparator(); switch ($placement) { case self::PREPEND: return $markup . $separator . $content; case self::APPEND: default: return $content . $separator . $markup; } } } class My_Decorator_SimpleLabel extends Zend_Form_Decorator_Abstract { protected $_format = '<label for="%s">%s</label>'; public function render($content) { $element = $this->getElement(); $id = htmlentities($element->getId()); $label = htmlentities($element->getLabel()); $markup = sprintf($this->_format, $id, $label); $placement = $this->getPlacement(); $separator = $this->getSeparator(); switch ($placement) { case self::APPEND: return $content . $separator . $markup; case self::PREPEND: default: return $markup . $separator . $content; } } }
请注意,在上面我正在为每个切换默认大小写;假设是标签前置内容,输入附加。
现在,让我们创建一个使用这些的表单元素:
$element = new Zend_Form_Element('foo', array( 'label' => 'Foo', 'belongsTo' => 'bar', 'value' => 'test', 'prefixPath' => array('decorator' => array( 'My_Decorator' => 'path/to/decorators/', )), 'decorators' => array( 'SimpleInput', 'SimpleLabel', ), ));
这将如何运作?当我们调用render()
时,元素将遍历各种附加的装饰器,对每个装饰器调用render()
。它会将一个空字符串传递给第一个,然后将创建的任何内容传递给下一个,依此类推:
- 初始内容是一个空字符串:”
- ”被传递给SimpleInput装饰器,然后生成一个表单输入,它附加到空字符串:
- 然后输入作为内容传递给SimpleLabel装饰器,它生成一个标签并将其添加到原始内容之前;默认分隔符是
PHP_EOL
字符,给我们这样的:
但等一下!如果您出于某种原因希望标签出现在输入之后怎么办?还记得那个“放置”标志吗?您可以将其作为选项传递给装饰器。最简单的方法是在元素创建期间通过装饰器传递一组选项:
$element = new Zend_Form_Element('foo', array( 'label' => 'Foo', 'belongsTo' => 'bar', 'value' => 'test', 'prefixPath' => array('decorator' => array( 'My_Decorator' => 'path/to/decorators/', )), 'decorators' => array( 'SimpleInput', array('SimpleLabel', array('placement' => 'append')), ), ));
注意传递选项时,必须将装饰器包裹在一个数组中;这向构造函数提示选项可用。装饰器名称是数组的第一个元素,选项在数组中传递给数组的第二个元素。
上面的标记。
使用这种技术,您可以拥有针对元素或表单的特定元数据的装饰器,并仅创建与该元数据相关的标记;通过使用多个装饰器,您可以构建完整的元素标记。我们的洋葱就是结果。
这种方法有利也有弊。先说缺点:
- 实施起来更复杂。您必须特别注意您使用的装饰器以及您使用的位置,以便以正确的顺序构建标记。
- 需要更多资源。更多的装饰器意味着更多的对象;将其乘以表单中的元素数量,最终可能会严重占用资源。缓存在这里可以提供帮助。
不过,这些优势是引人注目的:
- 可重复使用的装饰器。您可以使用此技术创建真正可重用的装饰器,因为您不必担心完整的标记,而只需为一个或几个元素/表单元数据创建标记。
- 终极灵活性。理论上,您可以从少量装饰器生成您想要的任何标记组合。
虽然上面的示例是装饰器在Zend_Form
中的预期用途,但通常很难理解装饰器如何相互交互以构建最终标记。出于这个原因,在1.7系列中增加了一些灵活性,使渲染单个装饰器成为可能——这为渲染表单提供了一些类似于Rails的简单性。请收看下一期以了解其中的一些技巧。
更新
- 2009-04-0616:00-0500:根据Mark的评论更新了SimpleLabel中的追加/前置
- 2009-04-0708:50-0500:修复了两个示例中的拼写错误,permzeis
- 2009-04-1209:35-0500:根据JosephM的注释,在两个示例中将sprint固定为sprintf。
本系列其他文章
- 最简单的Zend_Form装饰器