求解如何通过yii做form builder表单生成器,可动态验证,可任意添加/删除字段呢?
求解如何通过yii做form builder表单生成器,可动态验证,可任意添加/删除字段呢?
你问了一个极其复杂的问题,不过还是可以实现的 表单构造器 先参考此文 ,然后难点在于用表单构造器时需要一个模型 和一个配置文件
这个模型需要是动态的 不然一个配置文件就需要一个模型 现在只要设计出这个动态模型 用任何配置文件都可以跟其配合 那么就可以渲染出表单;这个类花费了我大量时间 但还是共享出来 有些方法名不是太合理 但功能还是可以用的
<?php
/**
用法:
* 控制器中:
*
$formConfig = array(
'title'=>'Please provide your login credential',
'elements'=>array(
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'rememberMe'=>array(
'type'=>'checkbox',
)
),
'buttons'=>array(
'login'=>array(
'type'=>'submit',
'label'=>'Login',
),
),
);
$formConfig = array(
'elements'=>array(
'firstname'=>array(
'type'=>'text',
'maxlength'=>40,
),
'lastname'=>array(
'type'=>'text',
'maxlength'=>40,
),
'membersince'=>array(
'type'=>'dropdownlist',
//it is important to add an empty item because of new records
'items'=>array(''=>'-',2009=>2009,2010=>2010,2011=>2011,),
),
),
'buttons'=>array(
'submit'=>array('type'=>'submit','label'=>'提交'),
),
);
$form = new DynamicFormModel ;//LoginForm;
$form->setAttributeNames(array_keys($formConfig['elements']));
$form = new CForm($formConfig, $form);
if(isset($_POST['DynamicForm'])) {
$model->attributes = $_POST['DynamicForm'];
print_r($model->getModelData());
//print_r($_POST);
}
$this->render('dynamicForm', array('form'=>$form));
视图中:
<div class="form">
<?php
echo $form; ?>
</div>
*
*
*
*
*
*
*/
/**
* 动态表单模型 多个表单配置可以共用同一个表单模型
* 需要一个表单配置数组或者文件(其中存放的是表单配置)
*
*
* -------------------------------------------------
* 可能需要复写:setAttributes 方法 来为模型赋值
* 同时复写魔术方法 __set() 有时间了做
* -------------------------------------------------
* 2011/7/27 0:38 add:
* 可以使用 addRule 动态添加一个验证规则
*
* 可以使用 $model->attributes = array('key'=>$value..'keyX'=>$valueX.....);
*
* 注意设置验证规则:一定要在 $model->attributes = $_POST['DynamicFormModel'];
* 之前完成 ,总之以前是 静态设计部分的内容 现在均改为动态的了
*
* 所有的操作尽可能一实例化就做 。比如设置label 设置 attribute
*
* 唯独 $model->attributes = $someArray 往后推
*
* ---------------------------------------------------
*/
class DynamicFormModel extends CFormModel
{
//...................................................
/**
* @static
* @param string $modelClass 你需要模型名字是什么
* @param string $scenario 场景 用来归类验证规则的
* @return DynamicFormModel 的子类
* 动态生成了一个类 该类继承自DynamicFormModel 在往上的父类是CFormModel
* 注意使用了eval
*/
public static function &createFormModel($modelClass='',$scenario = ''){
if(strcasecmp($modelClass,__CLASS__) === 0){
//类名跟本类相等
$modelClass = __CLASS__;
}elseif(!class_exists($modelClass,false)){
eval('class ' . $modelClass . ' extends DynamicFormModel { }');
}
$model = new $modelClass($scenario); //LoginForm;
return $model;
}
/**
* @static
* @param array $formElementConfig
* @param string $modelName
* @return void
* ----------------------------------------
* 根据单个表单元素的配置渲染输出
* 根据内部实现 这个输出应该是很费资源的
* 所以尽量少用 或者只是为了调试。
* ----------------------------------------
*/
public static function renderFormElements($formElementConfig=array(),$modelName=__CLASS__){
$formModel = self::createFormModel($modelName);
$formModel->setAttributeNames(array_keys($formElementConfig));
$form = new CForm(array('elements'=>$formElementConfig), $formModel);
//没有 activeForm 就不能提取元素的子 所以不得已 先抛弃一些内融
// CVarDumper::dump($formModel);
$form->renderBegin(); //只是字符串而已 不输出没啥问题 但不调用这个方法就不能实例化$_activeForm
$outPut = '';
foreach ($form->getElements() as $element) {
$outPut .= $element->render();
}
$form->renderEnd();//同renderBegin方法一样 幌子而已 看实现
return $outPut;
}
/**
* @static
* @throws CException
* @param $config
* @param string $modelName
* @param string $scenario
* @return CForm
* 根据配置文件渲染表单
*/
public static function renderForm($config,$modelName='Test',$scenario = ''){
if(is_string($config))
$config=require(Yii::getPathOfAlias($config).'.php');
if(! is_array($config)){
throw new CException('config must be an array or config file which is a path alias ! ');
}
//$formModel = self::createFormModel($modelName);
$formModel = new self($scenario);
$formModel->setAttributeNames(array_keys($config['elements']));
$form = new CForm($config, $formModel);
//还需要一个替换 就是表单名 有空做
$form = str_replace(array(__CLASS__.'_',__CLASS__.'['),array($modelName.'_',$modelName.'['),''.$form);
return $form;
}
//=========<原始实现 此节内容可在必要时修改掉 估计需要改掉的是attributeNames()方法的实现>==============================================================
private static $_names = array();
/**
* Constructor.
* @param string $scenario name of the scenario that this model is used in.
* See {@link CModel::scenario} on how scenario is used by models.
* @see getScenario
*/
public function __construct($scenario = '')
{
$this->setScenario($scenario);
$this->init();
$this->attachBehaviors($this->behaviors());
$this->afterConstruct();
}
/**
* Initializes this model.
* This method is invoked in the constructor right after {@link scenario} is set.
* You may override this method to provide code that is needed to initialize the model (e.g. setting
* initial property values.)
* @since 1.0.8
*/
public function init()
{
}
/**
* Returns the list of attribute names.
* By default, this method returns all public properties of the class.
* You may override this method to change the default.
* @return array list of attribute names. Defaults to all public properties of the class.
*/
public function attributeNames()
{
$className = get_class($this);
if (!isset(self::$_names[$className])) {
$names = array();
$attributeNames = array_keys($this->_modelData);
foreach ($attributeNames as $attributeName)
{
$names[] = $attributeName;
}
return self::$_names[$className] = $names;
}
else
return self::$_names[$className];
/*
* 原码实现在下面
* --------------------------------------------
$className=get_class($this);
if(!isset(self::$_names[$className]))
{
$class=new ReflectionClass(get_class($this));
$names=array();
foreach($class->getProperties() as $property)
{
$name=$property->getName();
if($property->isPublic() && !$property->isStatic())
$names[]=$name;
}
return self::$_names[$className]=$names;
}
else
return self::$_names[$className];
*/
}
//.....................................................................................
/**
* Returns all attribute values.
* @param array $names list of attributes whose value needs to be returned.
* Defaults to null, meaning all attributes as listed in {@link attributeNames} will be returned.
* If it is an array, only the attributes in the array will be returned.
* @return array attribute values (name=>value).
*/
public function getAttributes($names = null)
{
$values = array();
foreach ($this->attributeNames() as $name)
$values[$name] = $this->$name;
if (is_array($names)) {
$values2 = array();
foreach ($names as $name)
$values2[$name] = isset($values[$name]) ? $values[$name] : null;
return $values2;
}
else
return $values;
}
/**
* Sets the attribute values in a massive way.
* @param array $values attribute values (name=>value) to be set.
* @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
* A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
* @see getSafeAttributeNames
* @see attributeNames
*/
public function setAttributes($values, $safeOnly = true)
{
if (!is_array($values))
return;
//........................<<修改的地方..............................
$this->setAttributeNames(array_keys($values));
//........................<<修改的地方/>>..............................
$attributes = array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
foreach ($values as $name => $value)
{
if (isset($attributes[$name]))
$this->$name = $value;
else if ($safeOnly)
$this->onUnsafeAttribute($name, $value);
}
}
//=========<原始实现 此节内容是原始类中的代码/>==============================================================
/**
* @var array
* 模型数据
*/
protected $_modelData = array();
/**
* @var array
* 验证规则
*/
protected $_rules = array();
/**
* @var array
* 属性标签
*/
protected $_attributeLabels = array();
/**
* @param array $attributeNames
* @return void
* 设置表单的属性名
*/
public function setAttributeNames(array $attributeNames)
{
foreach($attributeNames as $name)
$this->addAttributeName($name);
}
public function addAttributeName($name){
if(!array_key_exists($name, $this->_modelData))
$this->_modelData[$name] = null;
}
//...........................................................................................
/*
* 模拟softDocument 的思路
public function initSoftAttribute($name)
{
if(!array_key_exists($name, $this->_attributeNames))
$this->_attributeNames[$name] = null;
}
public function initSoftAttributes($attributes)
{
foreach($attributes as $name)
$this->initSoftAttribute($name);
}
*/
//...........................................................................................
/**
* @param array $modelData
* @return void
* 为 模型设置值 键值对形式
* array(
* 'key1' => $value .....
* );
*
* -------------------------------------------
* 此方法语义跟 setAttribute 一样 或许需要复写那个方法
*
* -------------------------------------------
*/
public function setModelData(array $modelData)
{
$this->_modelData = $modelData;
}
/**
* @return array
* 返回表单数据
*-------------------------------
* 在块赋值后可以获取到赋值了的数据:
* 不过貌似没有必要了 这个返回的是
* 数组 跟$this->attributes 估计同义
*
*/
public function getModelData()
{
return $this->_modelData;
}
/**
* @param array $rules
* @return void
* ----------------------------------
* 设置验证规则 格式如CModel::rules()中
* 的东西
* -----------------------------------
*/
public function setRules(array $rules)
{
// $this->_rules = $rules;
foreach($rules as $rule){
$this->addRule($rule);
}
}
/**
* @return array
* 获取设置的规则
* 只是为_rules 提供getter/setter 而已
*/
public function getRules()
{
return $this->_rules;
}
public function addRule($validateRule)
{
if(is_array($validateRule) && !property_exists($this,$validateRule[0])){
$this->addAttributeName($validateRule[0]);
}
$this->_rules[] = $validateRule;
}
public function rules()
{
$attributeNames = array_keys($this->_modelData);
/**
* 如果设置了验证规则就用
* 设置的
* 如果没设置那么 所有的属性被认为是安全的
*/
if (!empty($this->_rules)) {
return $this->_rules;
} elseif (!empty($attributeNames)) {
return array(array(implode(', ', $attributeNames), 'safe'),
);
}else{
return array();
}
}
/**
* @param array $attributeLabels
* @return void
* ------------------------
* 设置属性标签显示值
* key是属性名称 value是将要显示在views
* 中的label值
* ------------------------
*/
public function setAttributeLabels(array $attributeLabels)
{
$this->_attributeLabels = $attributeLabels;
}
/**
* @return array
*
*/
public function attributeLabels()
{
return $this->_attributeLabels;
//return array_combine($this->_attributeNames,$this->_attributeNames);
}
/**
* @param $name
* @param $value
* @return void
* --------------------------------------
* 设置对象的此属性值
* -------------------------------------
*/
public function __set($name, $value)
{
try {
parent::__set($name, $value);
} catch (CException $ex) {
/**
* 这个地方其实还需要安全判断一下 是否是attributeNames的成员之一
* 或者判断一下 是否......
*/
$this->_modelData[$name] = $value;
//无法调用父类的同名方法 !;
}
}
/**
* @param $name
* @return mixed
* -----------------------------------------
* 获取指定attribute 的对象值
* 可以在这里弄默认值 呵呵
* -----------------------------------------
*/
public function __get($name)
{
if (array_key_exists($name,$this->_modelData)) {
return $this->_modelData[$name];
} else {
// print_r($this->_modelData);
return parent::__get($name);
}
}
public function __isset($name)
{
if(array_key_exists($name, $this->_modelData)) // Use of array_key_exists is mandatory !!!
return true;
else
return parent::__isset($name);
}
public function __unset($name)
{
if(array_key_exists($name, $this->_modelData)) // Use of array_key_exists is mandatory !!!
unset($this->_modelData[$name]);
else
parent::__unset($name);
}
public static function __callStatic($name,$arguments){
if( strcasecmp( $name ,'renderFormElement') === 0){
return call_user_func_array(array(__CLASS__,'renderFormElements'),$arguments);
}
}
//............<flash 上传文件的支持>.............................................................................
private $_filePostName;
/**
* @param string $filePostName
* @return DynamicFormModel
*/
public function setFilePostName($filePostName = 'Filedata')
{
$this->_filePostName = $filePostName;
return $this;
}
/**
* @return string
* 返回文件上传字段的名字
*/
public function getFilePostName(){
return $this->_filePostName;
}
private $_modelClassName;
/**
* @param string $modelName
* @return DynamicFormModel
*/
public function setModelClassName($modelName = '')
{
$this->_modelClassName = $modelName;
return $this;
}
/**
* @var string
* 上传文件的原始名称
*/
private $_originalUploadedFileName = '';
/**
* @return string
* 获取上传文件的原始名字
*/
public function getOriginalUpFileName(){
return $this->_originalUploadedFileName;
}
/**
* @var string
*/
protected $_destFilePath ;
/**
* @return string
* 获取最终文件上传后的路径地址
*/
public function getDestinationFilePath(){
return $this->_destFilePath;
}
/**
* @throws CException
* @param $destinationPath
* @param bool $preserveOriginalExtension 是否保留原有扩展作为新文件的名字
* @return bool
* --------------------------------------------
* 关于错误信息的转换 ajax验证时返回的错误信息
* 格式时 activeId => error;
* --------------------------------------------
*/
public function processUpload($destinationPath, $preserveOriginalExtension = false)
{
// throw new CException('to be continue!');
if (empty($this->_filePostName)) {
throw new CException('must give a filePostName');
}
//$this->addRule(array($this->_filePostName, 'file', 'allowEmpty' => false));
/**
* //数组转储:转为模型名是自己的类名,属性名为filePostName所传递的 如果是数组结构的文件字段名 Photo[path] 那么结果
* 等价于 DynamicFormMedel[path] 在$_FILES 数组中的结构
*/
$this->copyFilesArray();
// print_r($_FILES);
if(preg_match('/([^\[]+)\[(\w+)\]/',$this->_filePostName,$matches)){
// print_r($matches);
$filePostName = $matches[2];
$this->{$matches[2]} = array($matches[2]=>'');
$this->{$matches[2]} = CUploadedFile::getInstance($this, $matches[2]);
}else{
$filePostName = $this->_filePostName;
$this->addAttributeName($filePostName);
$this->{$this->_filePostName} = CUploadedFile::getInstance($this,$this->_filePostName);
// $this->{$this->_filePostName} = CUploadedFile::getInstanceByName($this->_filePostName);
// echo __LINE__, print_r($this->{$this->_filePostName});
}
//设置上传文件的原始路径
$this->_originalUploadedFileName = $this->{$filePostName}->getName();
if ($this->validate()) {
//如果保留原始扩展名:
if($preserveOriginalExtension == true){
$ext = $this->{$this->_filePostName}->getExtensionName();
$destinationPath = $destinationPath.'.'.$ext;
}
$this->_destFilePath = $destinationPath;
//验证通过了就处理上传
$this->{$filePostName}->saveAs($destinationPath);
return true;
} else {
//修改错误信息:
// $this->_error;
return false;
}
}
//..............<获取ajax错误格式>...................................
/**
* @return string
* 返回用户ajax验证的错误形式
*/
public function getErrorsAsJson($modelClass = __CLASS__){
$result = array();
foreach($this->getErrors() as $attribute=>$errors){
$result[CHtml::activeId($this,$attribute)] = $errors;
}
$result = function_exists('json_encode') ? json_encode($result) : CJSON::encode($result);
if($modelClass !== __CLASS__){
$result = str_replace(__CLASS__,$modelClass,$result);
}
return $result;
}
//..............<获取ajax错误格式/>...................................
/**
* @return void
* 拷贝 $_FILES 数组
* 真想说脏话 算了还是忍忍吧
*/
public function copyFilesArray()
{
if (strpos($this->_filePostName, '[') === false) {
// echo "whann";
$attribute = $this->_filePostName;
//没找到 数组形式的文件域字段名
$_FILES[__CLASS__] = array(
'name' => array($attribute => $_FILES[$this->_filePostName]['name']),
'type' => array($attribute => $_FILES[$this->_filePostName]['type']),
'tmp_name' => array($attribute => $_FILES[$this->_filePostName]['tmp_name']),
'error' => array($attribute => $_FILES[$this->_filePostName]['error']),
'size' => array($attribute => $_FILES[$this->_filePostName]['size']),
);
} else {
/**
*
* 麻烦了 要正则抓取 如User[icon] 抓取其中的User 和icon
* 然后用下面的方法来完成替换:
*
* $mathes[0] 是全匹配
* $matches[1] 是小括号中第一个子模式的匹配
* $matches[2] 是第二个子模式的匹配 这里就是中括号中的东西
*
**/
preg_match('/([^\[]+)\[(\w+)\]/',$this->_filePostName,$matches);
//print_r($matches);
$_FILES = $this->changeKeyNameRecursive(array($matches[1]=>__CLASS__),$_FILES);
}
}
/**
* @param array $originalKey_newKey
* @param $array
* @return array
* 递归替换掉 键一位新键
*/
private function changeKeyNameRecursive(array $originalKey_newKey, &$array)
{
foreach ($array as $k => $v) {
if (is_array($v)) {
$array[$k] = self::changeKeyNameRecursive($originalKey_newKey, $v);
}
if (isset($originalKey_newKey[$k])) {
$array[$originalKey_newKey[$k]] = $array[$k];
unset($array[$k]);
}
}
return $array;
}
//............<flash 上传文件的支持/>.............................................................................
}
/**
* 补充一些用法:
* $dm = new DynamicFormModel();
$dm->addRule(array(
'res_path', 'file', 'types' => 'exe',// 'jpg, gif, png',
'maxSize' => 1024 * 1024 * 50, // 50MB
'tooLarge' => 'The file was larger than 50MB. Please upload a smaller file.',
'minSize' => 1024*1024,
'tooSmall' => '文件太小了吧!至少大于0.5M哦!'
)
);
$dm->addRule(array(
'res_path',
'ext.validation.EPictureValidator',
'minWidth' => 20,
'minHeight' => 20,
)
);
$dm->setFilePostName('MediaRes[res_path]');
if($dm->processUpload('./data/tmp/hi.jpg')){
}else{
// ArrayUtil::dumpArray($dm->getErrors());
echo $dm->getErrorsAsJson('User');
}
ArrayUtil::dumpArray($_FILES);
die;
}
*
*
*
*/
是的阿,这个挺复杂的,到现在思路还不是很好。我试一下你说的这种方法。谢谢了!!!