承接 handcode/yii2-dynamic-form-model 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

handcode/yii2-dynamic-form-model

Composer 安装命令:

composer require handcode/yii2-dynamic-form-model

包简介

DynamicFormModel that is configurable with a given config struct

README 文档

README

DynamicFormModel implements a yii2 dynamicModel that is highly configurable with a config struct and provide methods to get attributes, the activeForm fields and input validation defined in the struct.

As the input (the struct and the attribute values) and the output (attribute values after validation) can be json, this class can be used to define dynamic ContentTypes where the struct of the type and the content values can or should be stored as json in DB.

As DynamicFormModel use yii2 base model functionality, activeForm fields, yii2 validation rules etc. you can define (nearly) all yii2 model validation and activeForm definitions that are possible.

  • default and custom validators, etc.
  • default activeFormFields, InputWidgets etc.

hint: attributes vs. properties

The term attribute(s) is highly used inside yii models to access properties and therefor we use the term 'properties' in the struct to define attributes ;-)

hint: closures

closures as option values are allowed in various places, but can only be used if the struct is plain PHP and NOT defined/used/safed as Json, as we can not serialize closures!

If you want to use callbacks in serializable structs you must define the callbacks as callable functions or (static) methods in your code and then set the name as callback in the struct.

dynamic properties (attributes)

Like in the yii2 base DynamicModel attributes and their values can be simply set via constructor.

In addition to the base DynamicModel class, the attributes (first constructor param) can also be provided as JSON string.

So this is all valid and equal:

$dynamicFormModelValues = ['pageId' => 5];
$dynamicFormModel = Yii::createObject(DynamicFormModel::class, [$dynamicFormModelValues]);
$dynamicFormModelValues = json_decode('{"pageId": 5}', JSON_OBJECT_AS_ARRAY);
$dynamicFormModel = Yii::createObject(DynamicFormModel::class, [$dynamicFormModelValues]);
$dynamicFormModelValues = '{"pageId": 5}';
$dynamicFormModel = Yii::createObject(DynamicFormModel::class, [$dynamicFormModelValues]);

The additional config struct can be applied by calling DynamicFormModel::setStruct($struct)

nested properties

Beside the "simple" definition of scalar attributes like strings, integer etc., properties can be of type object which allows us to define structured property sets. Each object property creates its own, auto-generated DynamicFormModel instance.

As child objects are full-featured DynamicFormModel instances, they can also have properties of type object, which will result in further child objects, which also can have objects, which....

The default input and rules definition from the "parent" level of the given struct are inherited to the child objects, but can of course also be overridden at any level.

To validate objects recursively a validation rule with the property name of the child object is added to its parent. The Validator in the rule is set to the configured DynamicFormModel::$objectPropertyValidationClass. Default implementation of such a validator is ObjectPropertyValidator

In order to preserve the nested structure of properties in html forms, the FormNames of the subobjects are automatically set accordingly.

To reflect the structure also in the HTML form while rendering the formFields, formFields of nested objects are encapsulated in (nested) fieldsets.

See the nested struct example below.

config struct

The struct is an array (or a json string that can be encoded to an array) with these keys:

  • formName (string): defines the Model scope via @see Model::formName()
  • rules (array|string): the (default)rules used for validating input values
  • properties (array): define the names that should be "public properties" of the model. if empty or not set, the keys from DynamicFormModel::getAttributes() is used
  • the properties can be:
    • string that simply define the property name
    • array where the key is the property name and the value is an array of configOptions for the property
    • if type is 'object' a nested config struct definition is expected
  • fieldSet: when defined the input for all properties that have NO self fieldSet defined will be rendered within a filedSet Tag with given name as set-legend. When not defined these inputs will not be renderd in any fieldSets
  • property options can be:
    • type: can be 'attribute' or 'object', if 'object' the property will be instantiated as a DynamicFormModel itself, see example below.
    • label: the label returned for this property, @see Model::getAttributeLabel()
    • hint: the hint returned for this property, @see Model::getAttributeHint()
    • rules: beside the defaultRules (see above) every property can define its own rules as subarray inside the properties
    • input: the config for the form input that will be rendered for the property when DynamicFormModel::formFields() is called
    • fieldSet: when defined the input for this property will be rendered within a filedSet Tag with given name as set-legend.

For the defined keys within the struct array explained above it is recommended to use the provided class constants.

For a more detailed definition of the struct schema see: ./docs/struct-schema

or read the code ;-) DynamicFormModel

formFields for the model

Beside the definition of properties and rules, the model provide a Method to get the ActiveFormFields for this model which can be displayed within an ActiveForm

This method will return form-fields for the given ActiveForm according to the dynamically assigned model properties and their input configs.

self::formFields($form)

grouping formFields

  • formFields can be grouped with the fieldSet option.
  • properties without fieldSet option, are added without fieldset or . if defined - to the fieldSet defined at root level.
  • to define nested groups of form elements, use nested object properties. see nested struct example in the examples below.

getting the values

as Array

As DynamicFormModel is an \yii\base\Model which implements the \yii\base\Arrayable interface the values can be accessed as array with the toArray() Method.

$model->toArray()

as JSON

To be able to persist the values of this model it implements the JsonSerializable interface and therefor can be stored as json simply be using

json_encode($model)

or with the wrapper method:

$model->toJson()

rules config:

Rules Config can be done

  • on the top level of the struct, which will than be the default for all properties which does not provide own rules config.
  • as subarray of a property config in the struct

Format:

  • The given rules can be an array of rule arrays or a string which is simply treated as the validator name if no options are required for the rule validator
  • The resulting array should be an array of valid validation rules WITHOUT the first param (the property name)
  • The property name will be appended dynamically while initializing the property rules from the given struct

Examples:

expected rule for e.g. property name should be:

$rules[] = [['name'], 'string', 'max' => 255]

the rules struct should be defined like:

['rules' => [
     ['string', 'max' => 255]
   ]
]

you can define multiple rules

expected rules for property name should be:

$rules[] = [['name'], 'trim'];
$rules[] = [['name'], 'string', 'max' => 255];

the rules struct should be defined like:

['rules' => [
   'trim',
   ['string', 'max' => 255]
]

if no "fancy" options are required, a rule can be defined as simple string:

['rules' => [
   'integer',
]

you can add filter rules with callbacks.

['rules' => [
    'integer',
    ['exist', 'skipOnError' => true, 'targetClass' => Page::class, 'targetAttribute' => ['pageId' => 'id']]
]

or

['rules' => [
      'trim',
      ['string', 'skipOnEmpty' => false, 'max' => 30],
      ['filter', 'filter' => 'strtolower', 'skipOnEmpty' => true],
      ['filter', 'filter' => [Inflector::class, 'slug'], 'skipOnEmpty' => true],
    ]
]

Hint closures in filter rules will only work, if the struct is plain PHP and NOT defined/safed as Json, as we can not serialize closures!

input config

Input Config can be done

  • on the top level of the struct, which will than be the default for all properties which does not provide their own input config.
  • as subarray of an property config in the struct

The struct of the input config looks like:

['input' = [
    'inputCallback' => null,
    'widget' => null,
    'type' => null,
    'options' => [],
    'items' => null,
    'itemValues' => null,
    'itemsCallback' => null,
    ]
]

For a more detailed definition of the struct schema see: ./docs/struct-schema

Examples

php struct examples

# init the model with values from e.g. json string stored in the DB
$dynamicFormModelValues = !empty($this->params) ? \yii\helpers\Json::decode($this->params) : [];
$dynamicFormModel       = Yii::createObject(DynamicFormModel::class, [$dynamicFormModelValues]);

# define the struct for the model, its properties, rules and input types.
$dynamicFromConfig =
    [
        DynamicFormModel::KEY_NAME => 'dynamicPageParamsModel',
        DynamicFormModel::KEY_PROPERTIES => [
            'pageId' => [
                DynamicFormModel::KEY_RULES => [
                    'integer'
                ],
                DynamicFormModel::KEY_INPUT => [
                    DynamicFormModel::KEY_INPUT_TYPE => 'dropDownList',
                    // define callback which should provide the items array for the dropDownList
                    // ! be aware, that only callback config like this, which is serializable as
                    // ! json can be used IF the config should be read/stored as (json)string, e.g. in a DB
                    DynamicFormModel::KEY_INPUT_LIST_ITEMS_CALLBACK => [
                        Page::class,
                        'getOpts'
                    ],
                ],
            ],
            'password' => [
                // simply define input type = password
                DynamicFormModel::KEY_INPUT => 'password'
            ],
            'title' => [
                DynamicFormModel::KEY_LABEL => 'Title param',
                DynamicFormModel::KEY_HINT => 'String that will be used as value for the title attribute of the model'
            ],
            'intMask' => [
                DynamicFormModel::KEY_HINT => 'test for yii2 MaskedInput widget',
                DynamicFormModel::KEY_INPUT => [
                    // define the inputWidgetClass and it's options that should be initialized
                    // when the form field for this property is rendered.
                    DynamicFormModel::KEY_INPUT_WIDGET => MaskedInput::class,
                    DynamicFormModel::KEY_INPUT_OPTIONS => [
                        'mask' => '99-99',
                    ],
                ],
            ],
            'status' => [
                DynamicFormModel::KEY_INPUT => [
                    // define callback which should provide the formInput for this property
                    // ! be aware, that callback config like this, which is NOT serializable as
                    // ! json can NOT be used IF the config should be read/stored as (json)string
                    // ! e.g. in a DB
                    DynamicFormModel::KEY_INPUT_CALLBACK => function($form, $model, $property, $options) {
                        $items = [0 => YII::t('app', 'inactive'), 1 => YII::t('app', 'active')];
                        $model->$property ?: $model->$property = 0;
                        return $form->field($model, $property)->radioList($items, $options);
                    },
                ]
            ]
        ],
        DynamicFormModel::KEY_INPUT => ['text'],
        DynamicFormModel::KEY_RULES => [
            'trim',
            ['string', 'skipOnEmpty' => false, 'max' => 20]
        ]
    ];
$dynamicFormModel->setStruct($dynamicFromConfig);
?>
<!-- ... anywhere inside an ActiveForm block -->
<div class="card">
    <div class="card-header">
        <label><?= \yii\helpers\Inflector::titleize('params') ?></label>
    </div>
    <div class="card-body">
        <?= $dynamicFormModel->formFields($form) ?>
    </div>
</div>
<!-- ... anywhere inside an ActiveForm block -->

The generated HTML FormFields will look like:

<div class="form-group field-dynamicpageparamsmodel-pageid has-success">
  <label class="control-label" for="dynamicpageparamsmodel-pageid">Page Id</label>
  <select id="dynamicpageparamsmodel-pageid" class="form-control" name="dynamicPageParamsModel[pageId]"
          aria-invalid="false">
    <option value="1">Home</option>
    <option value="2" selected="">Page2</option>
    <option value="3">Page3</option>
    <option value="5">Privacy</option>
  </select>

  <div class="help-block"></div>
</div>
<div class="form-group field-dynamicpageparamsmodel-password has-success">
  <label class="control-label" for="dynamicpageparamsmodel-password">Your password</label>
  <input type="password" id="dynamicpageparamsmodel-password" class="form-control"
         name="dynamicPageParamsModel[password]" aria-invalid="false">

  <div class="help-block"></div>
</div>
<div class="form-group field-dynamicpageparamsmodel-title">
  <label class="control-label" for="dynamicpageparamsmodel-title">URL label</label>
  <input type="text" id="dynamicpageparamsmodel-title" class="form-control" name="dynamicPageParamsModel[title]"
         value="page title">
  <div class="hint-block">String that will be used as SEO part of the url</div>
  <div class="help-block"></div>
</div>
<div class="form-group field-dynamicpageparamsmodel-intmask">
  <label class="control-label" for="dynamicpageparamsmodel-intmask">Int Mask</label>
  <input type="text" id="dynamicpageparamsmodel-intmask" class="form-control" name="dynamicPageParamsModel[intMask]"
         data-plugin-inputmask="inputmask_24adc0a5" inputmode="text">
  <div class="hint-block">test for yii2 MaskedInput widget here</div>
  <div class="help-block"></div>
</div>
<div class="form-group field-dynamicpageparamsmodel-status has-success">
  <label class="control-label">Status</label>
  <input type="hidden" name="dynamicPageParamsModel[status]" value="">
  <div id="dynamicpageparamsmodel-status" role="radiogroup" aria-invalid="false">
    <label><input type="radio" name="dynamicPageParamsModel[status]" value="0">inactive</label>
    <label><input type="radio" name="dynamicPageParamsModel[status]" value="1" checked=""> active</label></div>
  <div class="help-block"></div>
</div>  

nested struct example

Json struct definition with object properties to group the attributes

{
  "properties": {
    "backgroundImage": {
      "fieldset": "image"
    },
    "textBlock": {
      "type": "object",
      "properties": {
        "textInputs": {
          "type": "object",
          "properties": {
            "headline": {
            },
            "subline": {
            }
          }
        },
        "fontColor": {
          "fieldset": "options",
          "input": {
            "type": "color"
          }
        }
      },
      "rules": [
        "trim",
        "required",
        {
          "0": "string",
          "skipOnEmpty": true,
          "max": 120
        }
      ]
    }
  },
  "input": [
    "text"
  ],
  "rules": [
    "trim",
    {
      "0": "string",
      "skipOnEmpty": false,
      "max": 20
    }
  ]
}

The generated HTML FormFields will look like:

<fieldset>
    <legend>Image</legend>
    <div class="form-group field-playground-model-backgroundimage">
        <label class="control-label" for="playground-model-backgroundimage">Background Image</label>
        <input type="text" id="playground-model-backgroundimage" class="form-control"
               name="playground-model[backgroundImage]" value="">

        <div class="help-block"></div>
    </div>
</fieldset>
<fieldset>
    <legend>Text block</legend>
    <fieldset>
        <legend>Text inputs</legend>
        <div class="form-group field-playground-model-textblock-textinputs-headline required">
            <label class="control-label" for="playground-model-textblock-textinputs-headline">Headline</label>
            <input type="text" id="playground-model-textblock-textinputs-headline" class="form-control"
                   name="playground-model[textBlock][textInputs][headline]" value="headline value" aria-required="true">

            <div class="help-block"></div>
        </div>
        <div class="form-group field-playground-model-textblock-textinputs-subline required">
            <label class="control-label" for="playground-model-textblock-textinputs-subline">Subline</label>
            <input type="text" id="playground-model-textblock-textinputs-subline" class="form-control"
                   name="playground-model[textBlock][textInputs][subline]" value="subline value" aria-required="true">

            <div class="help-block"></div>
        </div>
    </fieldset>
    <fieldset>
        <legend>Options</legend>
        <div class="form-group field-playground-model-textblock-fontcolor required">
            <label class="control-label" for="playground-model-textblock-fontcolor">Font Color</label>
            <input type="color" id="playground-model-textblock-fontcolor" class="form-control"
                   name="playground-model[textBlock][fontColor]" value="#000000" aria-required="true">

            <div class="help-block"></div>
        </div>
    </fieldset>
</fieldset>

The submitted form data will look like:

[
    'backgroundImage' => 'image name'
    'textBlock' => [
        'textInputs' => [
            'headline' => 'headline value'
            'subline' => 'subline value'
        ]
        'fontColor' => '#000000'
    ]
]

struct validation

To validate the struct schema of a DynamicFormModel instance use the validateStruct() method. If not valid, struct errors can be accessed withc the getStructErrors() method.

If you want to validate a given struct, without or before initializing a DynamicFormModel, you can use the DynamicFormModelStruct which itself is a DynamicModel with "funny" validation methods to ensure the given struct is valid for DynamicFormModel.

Here is an example of an inline validation method for a json_struct attribute of an ActiveRecord which should be saved in a db:

    public function validateJsonStruct($attribute, $params, $validator)
    {
        $struct = $this->$attribute;
        if (is_string($struct)) {
            $struct = json_decode($struct, true);
        }
        # init validation model with the struct that should be validated
        $validationModel = new DynamicFormModelStruct($struct);
        # as we want to safe the struct as json, we disallow closures as callbacks
        $validationModel->allowClosures = false;
        # validate the struct
        $validationModel->validate();
        # and if the validation model has errors, we add them to the "main Model attribute"
        # to be able to find the errors in the struct, the validation model adds the "path" (e.g. properties.title.type) as error key.
        if ($validationModel->hasErrors()) {
            foreach ($validationModel->getErrors() as $key => $error) {
                if (is_string($error)) {
                    $this->addError($attribute, implode(': ', [$key, $error]));
                }
                if (is_array($error)) {
                    foreach ($error as $errorMsg) {
                        $this->addError($attribute, implode(': ', [$key, $errorMsg]));
                    }
                }
            }
        }
    }

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: BSD-3-Clause
  • 更新时间: 2026-07-09

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固