要创建一个简单输出表单输入标签的函数是非常容易的,比如<input type="text" name="whatever">
。然而,为了使表单生成器具有通用性,我们需要考虑更多的情况。下面是基本输入标签之外的一些其他考虑因素。
如何做...
1.首先,我们定义一个Application\Form\Generic
类。这个类以后也将作为专门的表单元素的基类。
复制 namespace Application \ Form ;
class Generic
{
// 一些代码 ...
}
2. 接下来,我们定义了一些类常量,这些常量一般会在表单元素生成中使用。
3. 前三个将成为与单个表单元素的主要组件相关联的键。然后我们定义支持的输入类型和默认值。
复制 const ROW = 'row' ;
const FORM = 'form' ;
const INPUT = 'input' ;
const LABEL = 'label' ;
const ERRORS = 'errors' ;
const TYPE_FORM = 'form' ;
const TYPE_TEXT = 'text' ;
const TYPE_EMAIL = 'email' ;
const TYPE_RADIO = 'radio' ;
const TYPE_SUBMIT = 'submit' ;
const TYPE_SELECT = 'select' ;
const TYPE_PASSWORD = 'password' ;
const TYPE_CHECKBOX = 'checkbox' ;
const DEFAULT_TYPE = self:: TYPE_TEXT;
const DEFAULT_WRAPPER = 'div' ;
4. 接下来,我们可以定义属性和一个设置属性的构造函数。
5. 在这个例子中,我们需要两个属性,$name
和$type
,因为没有这些属性我们就不能有效地使用元素。其他构造参数是可选的。此外,为了使一个表单元素基于另一个表单元素,我们包含了一个规定,即第二个参数$type
可以是Application\Form\Generic
的一个实例,在这种情况下,我们只需运行getters
(后面讨论)来填充属性。
复制 protected $name;
protected $type = self:: DEFAULT_TYPE;
protected $label = '' ;
protected $errors = array ();
protected $wrappers;
protected $attributes; // HTML 表单属性
protected $pattern = '<input type="%s" name="%s" %s>' ;
public function __construct ($name ,
$type ,
$label = '' ,
array $wrappers = array () ,
array $attributes = array () ,
array $errors = array ())
{
$this -> name = $name;
if ($type instanceof Generic ) {
$this -> type = $type -> getType () ;
$this -> label = $type -> getLabelValue () ;
$this -> errors = $type -> getErrorsArray () ;
$this -> wrappers = $type -> getWrappers () ;
$this -> attributes = $type -> getAttributes () ;
} else {
$this -> type = $type ?? self:: DEFAULT_TYPE;
$this -> label = $label;
$this -> errors = $errors;
$this -> attributes = $attributes;
if ($wrappers) {
$this -> wrappers = $wrappers;
} else {
$this -> wrappers[ self:: INPUT][ 'type' ] =
self:: DEFAULT_WRAPPER;
$this -> wrappers[ self:: LABEL][ 'type' ] =
self:: DEFAULT_WRAPPER;
$this -> wrappers[ self:: ERRORS][ 'type' ] =
self:: DEFAULT_WRAPPER;
}
}
$this -> attributes[ 'id' ] = $name;
}
注意,$wrappers
有三个主要的子键。INPUT
, LABEL
, 和ERRORS
. 这允许我们为标签、输入标签和错误定义单独的包装器。
6. 在定义为标签、输入标签和错误产生HTML的核心方法之前,我们应该定义一个 getWrapperPattern()
方法,该方法将为标签、输入和错误显示产生相应的包装标签。
7. 例如,如果包装器被定义为<div>
,并且它的属性包括['class'=>'label']
,那么这个方法将返回一个sprintf()
格式的模式,看起来像这样。<div class="label">%s</div>
。例如,最终产生的HTML标签将替换%s
。
8. 下面是getWrapperPattern()
方法的样子。
复制 public function getWrapperPattern ($type)
{
$pattern = '<' . $this -> wrappers[$type][ 'type' ];
foreach ( $this -> wrappers[$type] as $key => $value) {
if ($key != 'type' ) {
$pattern .= ' ' . $key . '="' . $value . '"' ;
}
}
$pattern .= '>%s</' . $this -> wrappers[$type][ 'type' ] . '>' ;
return $pattern;
}
9. 现在我们准备好定义getLabel()
方法了。这个方法需要做的就是使用sprintf()
将标签插入到包装器中。
复制 public function getLabel ()
{
return sprintf ( $this -> getWrapperPattern ( self:: LABEL ),
$this -> label ) ;
}
10. 为了生成核心输入标签,我们需要有一种方法来组装属性。幸运的是,只要将它们以关联数组的形式提供给构造函数,就可以轻松实现这一点。在这种情况下,我们需要做的就是定义一个 getAttribs()
方法,该方法产生一个由键值对组成的字符串,并以空格分隔。我们使用trim()
返回最终值,以去除多余的空格。
11. 如果元素包含value
或href
属性,则出于安全原因,我们假定这些值是用户提供的(因此值得怀疑),所以我们需要转义。 因此,我们需要添加一个if
语句来检查然后使用htmlspecialchars()
或urlencode()
:
复制 public function getAttribs ()
{
foreach ( $this -> attributes as $key => $value) {
$key = strtolower ( $key ) ;
if ($value) {
if ($key == 'value' ) {
if ( is_array ( $value ) ) {
foreach ($value as $k => $i)
$value[$k] = htmlspecialchars ( $i ) ;
} else {
$value = htmlspecialchars ( $value ) ;
}
} elseif ($key == 'href' ) {
$value = urlencode ( $value ) ;
}
$attribs .= $key . '="' . $value . '" ' ;
} else {
$attribs .= $key . ' ' ;
}
}
return trim ( $attribs ) ;
}
12. 对于核心的输入标签,我们将逻辑分成两个独立的方法。主要方法getInputOnly()
只产生HTML输入标签。第二个方法,getInputWithWrapper()
,产生嵌入包装器中的输入。分离的原因是,当创建衍生类时,例如生成单选按钮的类,我们将不需要包装器。
复制 public function getInputOnly ()
{
return sprintf ( $this -> pattern , $this -> type , $this -> name ,
$this -> getAttribs ()) ;
}
public function getInputWithWrapper ()
{
return sprintf ( $this -> getWrapperPattern ( self:: INPUT ),
$this -> getInputOnly ()) ;
}
13.现在我们定义一个显示元素验证错误的方法。我们假设错误是以数组的形式提供的,如果没有错误,我们返回一个空字符串。否则,错误将以<ul><li>error 1</li><li>error 2</li></ul>
等形式显示。
复制 public function getErrors ()
{
if ( ! $this -> errors || count ( $this -> errors == 0 ) ) return '' ;
$html = '' ;
$pattern = '<li>%s</li>' ;
$html .= '<ul>' ;
foreach ( $this -> errors as $error)
$html .= sprintf ( $pattern , $error ) ;
$html .= '</ul>' ;
return sprintf ( $this -> getWrapperPattern ( self:: ERRORS ), $html ) ;
}
14. 对于某些属性,我们可能需要对属性的各个方面进行更有限的控制。例如,我们可能需要在已经存在的错误数组中添加一个错误。另外,设置一个单一的属性也可能是有用的。
复制 public function setSingleAttribute ($key , $value)
{
$this -> attributes[$key] = $value;
}
public function addSingleError ($error)
{
$this -> errors[] = $error;
}
15. 最后,我们定义了getter
和setter
,允许我们检索或设置属性的值。例如,你可能已经注意到$pattern
的默认值是<input type="%s" name="%s" %s>
。对于某些标签(例如,选择和表单标签),我们需要将此属性设置为不同的值。
复制 public function setPattern ($pattern)
{
$this -> pattern = $pattern;
}
public function setType ($type)
{
$this -> type = $type;
}
public function getType ()
{
return $this -> type;
}
public function addSingleError ($error)
{
$this -> errors[] = $error;
}
// 定义类似的获取和设置方法
// 用于名称、标签、包装物、错误和属性。
16. 我们还需要添加能给出标签值(不是HTML)的方法,以及错误数组。
复制 public function getLabelValue ()
{
return $this -> label;
}
public function getErrorsArray ()
{
return $this -> errors;
}
如何运行...
请确保将前面所有的代码复制到一个单一的Application\Form\Generic
类中。然后你可以定义一个chap_06_form_element_generator.php
调用脚本来设置自动加载和锚定新类。
复制 <? php
require __DIR__ . '/../Application/Autoload/Loader.php' ;
Application \ Autoload \ Loader :: init ( __DIR__ . '/..' ) ;
use Application \ Form \ Generic ;
接下来,定义包装器。为了说明问题,我们将使用HTML表格数据和头标签。注意标签使用TH
,而输入和错误使用TD
。
复制 $wrappers = [
Generic :: INPUT => [ 'type' => 'td' , 'class' => 'content' ] ,
Generic :: LABEL => [ 'type' => 'th' , 'class' => 'label' ] ,
Generic :: ERRORS => [ 'type' => 'td' , 'class' => 'error' ]
];
现在你可以通过向构造函数传递参数来定义一个电子邮件元素。
复制 $email = new Generic ( 'email' , Generic :: TYPE_EMAIL , 'Email' , $wrappers ,
[ 'id' => 'email' ,
'maxLength' => 128 ,
'title' => 'Enter address' ,
'required' => '' ]);
或者,使用设置器定义密码元素。
复制 $password = new Generic ( 'password' , $email);
$password -> setType ( Generic :: TYPE_PASSWORD ) ;
$password -> setLabel ( 'Password' ) ;
$password -> setAttributes ( [ 'id' => 'password' ,
'title' => 'Enter your password' ,
'required' => '' ] ) ;
最后,一定要定义一个提交按钮。
复制 $submit = new Generic ( 'submit' ,
Generic :: TYPE_SUBMIT ,
'Login' ,
$wrappers ,
[ 'id' => 'submit' , 'title' => 'Click to login' , 'value' => 'Click Here' ]);
实际的显示逻辑可能是这样的。
复制 < div class= "container" >
<!-- Login Form -->
< h1 > Login </ h1 >
< form name = "login" method = "post" >
< table id = "login" class= "display"
cellspacing = "0" width = "100%" >
< tr ><?= $email -> render () ; ?></ tr >
< tr ><?= $password -> render () ; ?></ tr >
< tr ><?= $submit -> render () ; ?></ tr >
< tr >
< td colspan = 2 >
< br >
<? php var_dump ( $_POST ) ; ?>
</ td >
</ tr >
</ table >
</ form >
</ div >
下面是实际输出。