popphp/pop-form 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

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

popphp/pop-form

Composer 安装命令:

composer require popphp/pop-form

包简介

Pop Form Component for Pop PHP Framework

README 文档

README

Build Status Coverage Status

Join the chat at https://discord.gg/TZjgT74U7E

Overview

pop-form is a robust component for managing, rendering and validating HTML forms. With it, you can have complete control over how a form looks and functions as well as granular control over field validation. Features include:

  • Field element creation and configuration
  • Validation
    • Use any callable validation object, such as pop-validator or custom validators
  • Filtering
  • Dynamic field generation based on the fields of a database table

pop-formis a component of the Pop PHP Framework.

Top

Install

Install pop-form using Composer.

composer require popphp/pop-form

Or, require it in your composer.json file

"require": {
    "popphp/pop-form" : "^4.2.6"
}

Top

Quickstart

The most basic way to wire up a form object is through a simple configuration.

use Pop\Form\Form;

$fields = [
    'username' => [
        'type'     => 'text',
        'label'    => 'Username:',
        'required' => true
    ],
    'email' => [
        'type'  => 'email',
        'label' => 'Email:'
    ],
    'submit' => [
        'type'  => 'submit',
        'value' => 'SUBMIT'
    ]
];

$form = Form::createFromConfig($fields);

if ($_POST) {
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        echo 'Valid!';
    }
} else {
    echo $form;
}

The form rendered will look like:

<form action="#" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="email">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

Upon submit, if the form values do not pass validation, the form will re-render with the errors (note the error div under the username field):

<form action="/" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
                <div class="error">This field is required.</div>
            </dd>
            <dt>
                <label for="email">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="test@test.com" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

The form object will default to POST as the method and the current REQUEST_URI as the action, but those values can be changed in a number of ways:

$form = new Form($fields, , '/form-action', 'GET');
$form = Form::createFromConfig($fields, '/form-action', 'GET');
$form->setMethod('GET')
    ->setAction('/form-action');

Top

Field Elements

A form can be wired up by interfacing directly with form element objects and the form object itself.

use Pop\Form\Form;
use Pop\Form\Element\Input;
use Pop\Validator;

$form = new Form();
$form->setAttribute('id', 'my-form');

$username = new Input\Text('username');
$username->setLabel('Username:')
    ->setRequired(true)
    ->setAttribute('size', 40)
    ->addValidator(new Validator\AlphaNumeric());

$email = new Input\Email('email');
$email->setLabel('Email:')
    ->setRequired(true)
    ->setAttribute('size', 40);

$submit = new Input\Submit('submit', 'SUBMIT');

// Add a single field
$form->addField($username);

// Add multiple fields
$form->addFields([$email, $submit]);

if ($_POST) {
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        echo 'Valid!';
    }
} else {
    echo $form;
}

There are number of different concepts happening in the above example:

  1. We created the form object and gave it an id attribute.
  2. We created the individual field elements setting their name, label, attributes, validators, etc.
  3. We added the field elements to the form object.
  4. We checked for a $_POST submission. If not detected, we just render the form for the first time.
  5. If a $_POST submission is detected:
    1. Set the field values with the values in the $_POST array (a bad idea without any filtering)
    2. Check if the form object passes validation. If not, re-render the form with the errors. If it does pass, then you're good to go.

On the first pass, the form will render like this:

<form action="/" method="post" id="my-form">
    <fieldset d="my-form-fieldset-1" class="my-form-fieldset"></fieldset>
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" size="40" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" size="40" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

If it fails validation, it will render with the errors. In this case, the username was not alphanumeric:

<form action="/" method="post" id="my-form">
    <fieldset d="my-form-fieldset-1" class="my-form-fieldset"></fieldset>
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" size="40" />
                <div class="error">The value must only contain alphanumeric characters.</div>
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" size="40" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

Top

Field Configurations

We can do the same thing as above with a field configuration array, which helps streamline the process:

use Pop\Form\Form;
use Pop\Validator;

$fields = [
    'username' => [
        'type'       => 'text',
        'label'      => 'Username:',
        'required'   => true,
        'attributes' => [
            'size' => 40
        ],
        'validators' => [
            new Validator\AlphaNumeric()
        ]
    ],
    'email' => [
        'type'       => 'email',
        'label'      => 'Email:',
        'required'   => true,
        'attributes' => [
            'size' => 40
        ]
    ],
    'submit' => [
        'type'  => 'submit',
        'value' => 'SUBMIT'
    ]
];

$form = Form::createFromConfig($fields);
$form->setAttribute('id', 'my-form');

if ($_POST) {
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        echo 'Valid!';
    }
} else {
    echo $form;
}

Top

Fieldsets

Multiple fieldset configurations can be used to generate a larger form with more organized elements. This requires the config to contain multiple arrays of field configurations:

use Pop\Form\Form;

$fields = [
    [
        'username' => [
            'type'       => 'text',
            'label'      => 'Username:',
            'required'   => true,
        ],
        'email' => [
            'type'       => 'email',
            'label'      => 'Email:',
            'required'   => true,
        ],
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ],
    [
        'first_name' => [
            'type'  => 'text',
            'label' => 'First Name:',
        ],
        'last_name' => [
            'type'  => 'text',
            'label' => 'Last Name:',
        ],
    ],
    [
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ]
];

$form = Form::createFromFieldsetConfig($fields);

Which produces the following HTML with the appropriate fieldset grouping:

<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-2" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="first_name">First Name:</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name:</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-3" class="pop-form-fieldset">
        <dl>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

Top

Legends

If you'd like to label each of the multiple fieldsets, that can be done by using legend values as the array keys in the config:

use Pop\Form\Form;

$fields = [
    'Account Info' => [
        'username' => [
            'type'       => 'text',
            'label'      => 'Username:',
            'required'   => true,
        ],
        'email' => [
            'type'       => 'email',
            'label'      => 'Email:',
            'required'   => true,
        ],
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ],
    'Personal Info' => [
        'first_name' => [
            'type'  => 'text',
            'label' => 'First Name:',
        ],
        'last_name' => [
            'type'  => 'text',
            'label' => 'Last Name:',
        ],
    ],
    [
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ]
];

$form = Form::createFromFieldsetConfig($fields);

Which produces the following HTML with the appropriate fieldset grouping:

<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <legend>Account Info</legend>
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-2" class="pop-form-fieldset">
        <legend>Personal Info</legend>
        <dl>
            <dt>
                <label for="first_name">First Name:</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name:</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-3" class="pop-form-fieldset">
        <dl>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

Top

Field Containers

The default fieldset HTML containers for the form elements is a combination of dl, dt and dd tags. If alternate container tags are needed, you can set them like these examples below.

Using table:
$form = Form::createFromConfig($fields, 'table');
<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <table>
            <tr>
                <td>
                    <label for="username" class="required">Username:</label>
                </td>
                <td>
                    <input type="text" name="username" id="username" value="" required="required" size="40" />
                </td>
            </tr>
            <tr>
                <td>
                    <label for="email" class="required">Email:</label>
                </td>
                <td>
                    <input type="email" name="email" id="email" value="" required="required" size="40" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" name="submit" id="submit" value="SUBMIT" />
                </td>
            </tr>
        </table>
    </fieldset>
</form>
Using div (or any other single element container):
$form = Form::createFromConfig($fields, 'div');
<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <div>
            <label for="username" class="required">Username:</label>
            <input type="text" name="username" id="username" value="" required="required" size="40" />
        </div>
        <div>
            <label for="email" class="required">Email:</label>
            <input type="email" name="email" id="email" value="" required="required" size="40" />
        </div>
        <div>
            <input type="submit" name="submit" id="submit" value="SUBMIT" />
        </div>
    </fieldset>
</form>

Top

Filtering

As mentioned above, when dealing user-submitted values, it's a bad idea to use them or display them back on the screen without filtering them. A common set a filters to employ would be strip_tags and htmlentities. So in the first example, we would add filters to the $_POST block:

use Pop\Filter\Filter;

/** ... Code to create form **/

if ($_POST) {
    $form->addFilter(new Filter('strip_tags'))
         ->addFilter(new Filter('htmlentities', [ENT_QUOTES, 'UTF-8']));
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        $form->clearFilters();
        $form->addFilter(new Filter('html_entity_decode', [ENT_QUOTES, 'UTF-8']));
        echo 'Valid!';
    }
} else {
    echo $form;
}

Of course, the strip_tags filter will strip out any possible malicious tags. The htmlentities filter is useful if the form has to render with the values in it again:

<input type="text" name="username" id="username" 
    value="Hello&quot;World&quot;" required="required" size="40" />

Without the htmlentities filter, the quotes within the value would break the HTML of the input field. Of course, if you want to use the values after the form is validated, then you have to call clearFilters() and filter the values with html_entity_decode.

Top

Validation

Of course, one of the main reasons for using a form component such as this one is the leverage the validation aspect of it. You've already seen the use of a basic validator from the pop-validator component and those are easy enough to use. But, you can create your own custom validators by either extending the pop-validator component with your own or just writing your own custom callable validators. The only real rule that needs to be followed is that the custom validator must return null on success or a string message on failure that is then used in error display. Here are some examples:

Using a closure
$username = new Input\Text('username');
$username->addValidator(function ($value) {
    if (strlen($value) < 6) {
        return 'The username value must be greater than or equal to 6.';
    }
});
Using a validator
use Pop\Validator\AlphaNumeric;

$username = new Input\Text('username');
$username->addValidator(new AlphaNumeric());
Using a custom class
class MyValidator
{
    public function validate($value)
    {
        if (strlen($value) < 6) {
            return 'The username value must be greater than or equal to 6';
        }
    }
}

$username = new Input\Text('username');
$username->addValidator([new MyValidator(), 'validate']);
Validation-only forms

There is a FormValidator class that is available for only validating a set of field values. The benefit of this feature is to not be burdened with the concern of rendering an entire form object, and to only return the appropriate validation messaging. This is useful for things like API calls, where the form rendering might be handled by another piece of the application (and not the PHP server side).

use Pop\Form\FormValidator;
use Pop\Validator;

$validators = [
    'username' => new Validator\AlphaNumeric(),
    'password' => new Validator\LengthGte(6)
];

$form = new FormValidator($validators);
$form->setValues([
    'username' => 'admin$%^',
    'password' => '12345'
]);

if (!$form->validate()) {
    print_r($form->getErrors());
}

If the field values are bad, the $form->getErrors() will return an array of errors like this:

Array
(
    [username] => Array
        (
            [0] => The value must only contain alphanumeric characters.
        )

    [password] => Array
        (
            [0] => The value length must be greater than or equal to 6.
        )

)

Top

Dynamic Fields

The pop-form comes with the functionality to very quickly wire up form fields that are mapped to the columns in a database. It does require the installation of the pop-db component to work. Consider that there is a database table class called Users that is mapped to the users table in the database. It has six fields: id, username, password, first_name, last_name and email.

(For more information on using pop-db click here.)

use Pop\Form\Form;
use Pop\Form\Fields;
use MyApp\Table\Users;

// The 4th parameter is an 'omit' to prevent certain fields from displaying
$config = Fields::getConfigFromTable(Users::getTableInfo(), null, null, 'id');
$form   = Form::createFromConfig($config);
echo $form;

This will render like:

<form action="/" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="password" class="required">Password:</label>
            </dt>
            <dd>
                <input type="password" name="password" id="password" value="" required="required" />
            </dd>
            <dt>
                <label for="first_name" class="required">First Name:</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" required="required" />
            </dd>
            <dt>
                <label for="last_name" class="required">Last Name:</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" required="required" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" />
            </dd>
        </dl>
    </fieldset>
</form>

You can set element-specific attributes and values, as well as set fields to omit, like the 'id' parameter in the above examples. Any TEXT column type in the database is created as textarea objects and then the rest are created as input text objects.

Top

ACL Forms

ACL forms utilize the pop-acl component and are an extension of the regular form class that take an ACL object with its roles and resources and enforce which form fields can be seen and edited. Consider the following code below:

use Pop\Form;
use Pop\Acl;

$acl      = new Acl\Acl();
$admin    = new Acl\AclRole('admin');
$editor   = new Acl\AclRole('editor');
$username = new Acl\AclResource('username');
$password = new Acl\AclResource('password');

$acl->addRoles([$admin, $editor]);
$acl->addResources([$username, $password]);

$acl->deny($editor, 'username', 'edit');
$acl->deny($editor, 'password', 'view');

$fields = [
    'username' => [
        'type'  => 'text',
        'label' => 'Username'
    ],
    'password' => [
        'type'  => 'password',
        'label' => 'Password'
    ],
    'first_name' => [
        'type'  => 'text',
        'label' => 'First Name'
    ],
    'last_name' => [
        'type'  => 'text',
        'label' => 'Last Name'
    ],
    'submit' => [
        'type'  => 'submit',
        'value' => 'Submit'
    ]
];

$form = Form\AclForm::createFromConfig($fields);
$form->setAcl($acl);

The $admin has no restrictions. However, the $editor role does have restrictions and cannot edit the username field and cannot view the password field. Setting the $editor as the form role and rendering the form will look like this:

$form->addRole($editor);
echo $form;
<form action="#" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username">Username</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" readonly="readonly" />
            </dd>
            <dt>
                <label for="first_name">First Name</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="Submit" />
            </dd>
        </dl>
    </fieldset>
</form>

There is no password field and the username field has been made readonly. Switch the role to $admin and the entire form will render with no restrictions:

$form->addRole($admin);
echo $form;
<form action="#" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username">Username</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" />
            </dd>
            <dt>
                <label for="password">Password</label>
            </dt>
            <dd>
                <input type="password" name="password" id="password" value="" />
            </dd>
            <dt>
                <label for="first_name">First Name</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="Submit" />
            </dd>
        </dl>
    </fieldset>
</form>

popphp/pop-form 适用场景与选型建议

popphp/pop-form 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 20.22k 次下载、GitHub Stars 达 13, 最近一次更新时间为 2014 年 12 月 08 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「php」 「Forms」 「form validation」 「html forms」 「pop」 「pop php」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 popphp/pop-form 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 popphp/pop-form 我们能提供哪些服务?
定制开发 / 二次开发

基于 popphp/pop-form 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

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

GitHub 信息

  • Stars: 13
  • Watchers: 1
  • Forks: 3
  • 开发语言: PHP

其他信息

  • 授权协议: BSD-3-Clause
  • 更新时间: 2014-12-08