ZF3 - 如何在字段集集合中填充 Select-元素
ZF3 - Howto fill a Select-Element insinde a Collection of Fieldsets
以下场景:
我想通过 Zend Framework 3 表单编辑由 3 个字段(id、用户名和域)组成的 table 'accounts'。可以从一组域名中选择字段'domain'(这里是静态数组以简化事情)
我有一个带有 getter 和 setter 的简单实体 'AccountModel.php':
namespace Project\Model;
class AccountModel {
private $id;
private $userName;
private $domain;
public function getUserName(){
return $this->userName;
}
public function setUserName(string $userName){
$this->userName = $userName;
return $this;
}
public function getId() {
return $this->id;
}
public function setId(int $id) {
$this->id = $id;
return $this;
}
public function getDomain() {
return $this->domain;
}
public function setDomain($domain) {
$this->domain = $domain;
return $this;
}
}
和相应的字段集'AccountFieldset.php':
namespace Project\Form;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Form\Element\Text;
use Zend\Form\Element\Select;
use Project\Model\AccountModel;
use Zend\Hydrator\ClassMethods;
class AccountFieldset extends Fieldset implements
InputFilterProviderInterface {
const NAME_ID = 'id';
const NAME_USERNAME = 'username';
const NAME_DOMAIN = 'domain';
private $domainsOptionValues = [ ];
public function __construct(array $domains, $name = null, $options = null) {
parent::__construct ( isset ( $name ) ? $name : 'account-fieldset', $options );
$this->setHydrator ( new ClassMethods ( false ) )->setObject ( new AccountModel () );
$this->domainsOptionValues = $domains;
}
public function init() {
$this->add ( [
'name' => self::NAME_ID,
'type' => Text::class,
'options' => [
'label' => 'Id'
],
'attributes' => [
'required' => 'required',
'readonly' => true
]
] );
$this->add ( [
'name' => self::NAME_USERNAME,
'type' => Text::class,
'options' => [
'label' => 'Username'
],
'attributes' => [
'required' => 'required'
]
] );
$this->add ( [
'name' => self::NAME_DOMAIN,
'type' => Select::class,
'options' => [
'label' => 'Domains',
'value_options' => $this->domainsOptionValues
],
'attributes' => [
'required' => 'required'
]
] );
}
public function getInputFilterSpecification() {
return [
/** some InputFilterSpecifications **/
];
}
}
这个 Fieldset 将在另一个 Fieldset 中使用,它表示 table 'AccountTableFieldset.php':
namespace Project\Form;
use Project\Model\AccountsTableModel;
use Zend\Form\Fieldset;
use Zend\Hydrator\ClassMethods;
use Zend\InputFilter\InputFilterProviderInterface;
class AccountsTableFieldset extends Fieldset implements InputFilterProviderInterface {
const NAME_ACCOUNTS = 'accounts';
public function __construct($name = null, $options = []) {
$name = isset ( $name ) ? $name : 'accounts-table';
parent::__construct ( $name, $options );
$this->setHydrator ( new ClassMethods ( false ) )->setObject ( new AccountsTableModel () );
$this->add ( [
'type' => 'Zend\Form\Element\Collection',
'name' => 'accounts',
'options' => [
'label' => 'Accounts',
'count' => 1,
'should_create_template' => false,
'allow_add' => false,
'target_element' => [
'type' => AccountFieldset::class,
]
]
] );
}
public function getInputFilterSpecification() {
return [
/** some InputFilterSpecification **/
];
}
}
“AccountsTableModel.php”没有什么特别之处:
namespace Project\Model;
class AccountsTableModel {
private $accounts = [];
/**
* @return AccountModel[]
*/
public function getAccounts() : array {
return $this->accounts;
}
public function setAccounts(array $accounts) {
$this->accounts = $accounts;
return $this;
}
}
所以我的表格看起来像这样'AccountForm.php':
namespace Project\Form;
use Zend\Form\Form;
use Zend\Form\Element\Submit;
class AccountsForm extends Form {
const NAME_TABLE = 'accounts-table';
const NAME_SUBMIT= 'accounts-save';
public function init(){
$this->setName('accounts-form');
$this->add([
'name' => self::NAME_TABLE,
'type' => AccountsTableFieldset::class,
'attributes' => [
'class' => 'accounts',
'id' => 'accounts-table',
],
]);
$this->add([
'name' => self::NAME_SUBMIT,
'type' => Submit::class,
'attributes' => [
'class' => 'btn btn-success',
'value' => 'Save accounts',
'id' => 'accounts-save',
],
]);
}
}
此表单通过“AccountsFormFactory.php”中的 FormElementManager 初始化:
namespace Project\Factory;
use Interop\Container\ContainerInterface;
use Project\Form\AccountsForm;
use Zend\ServiceManager\Factory\FactoryInterface;
class AccountsFormFactory implements FactoryInterface{
public function __invoke(ContainerInterface $container){
$accountsForm = $container->get('FormElementManager')->get(AccountsForm::class);
$accountsForm->init();
return $accountsForm;
}
}
为了填充 AccountFieldset 中的 Select-元素,我创建了 '** AccountFieldsetFactory.php**':
namespace Project\Factory;
use Interop\Container\ContainerInterface;
use Project\Form\AccountFieldset;
class AccountFieldsetFactory {
public function __invoke(ContainerInterface $container){
$domains = [
'1' => 'example1.com',
'2' => 'example2.com',
'3' => 'example3.com',
];
$accountsFieldset = new AccountFieldset($domains);
$accountsFieldset->init();
// die(__FILE__ . ' #'. __LINE__);
return $accountsFieldset;
}
}
注意我让它死了。但不幸的是,这条线永远不会到达,因为 ElementFactory 直接调用了 AccountFieldset。此时我得到错误:
Uncaught TypeError: Argument 1 passed to Project\Form\AccountFieldset::__construct() must be of the type array, string given, called in /var/www/html/zf3.local/vendor/zendframework/zend-form/src/ElementFactory.php on line 70
为什么调用 ElementFactory 而不是我的 AccountFieldsetFactory?我将 'form_elements' 的 'factories' 配置如下 'ConfigProvider.php':
namespace MailManager;
use Zend\Db\Adapter;
use Project\Factory\FormElementManagerDelegatorFactory;
class ConfigProvider {
public function __invoke(){
return [
'dependencies' => $this->getDependencies(),
'routes' => $this->getRoutes(),
'templates' => $this->getTemplates(),
'form_elements' => [
'factories' => [
Form\AccountFieldset::class => Factory\AccountFieldsetFactory::class,
]
]
];
}
public function getDependencies(){
return [
'factories' => [
Action\AccountsAction::class => Factory\AccountsActionFactory::class,
Repository\AccountsRepositoryInterface::class => Factory\AccountsRepositoryFactory::class,
Storage\AccountsStorageInterface::class => Factory\AccountsStorageFactory::class,
Form\AccountsForm::class => Factory\AccountsFormFactory::class,
Form\NewAccountForm::class => Factory\NewAccountFormFactory::class,
Adapter\AdapterInterface::class => Adapter\AdapterServiceFactory::class,
],
'delegators' => [
'FormElementManager' => [
FormElementManagerDelegatorFactory::class,
],
],
];
}
public function getRoutes(){
return [
/** some routes **/
];
}
public function getTemplates(){
return [
'paths' => [
/** some paths **/
],
];
}
}
感谢 Configure FormElementManager #387 中的建议,FormElementManagerDelegatorFactory 工作正常并且 AccountFieldsetFactory 显示在 FormElementManager 的工厂部分中。但遗憾的是(如前所述)从未调用过 AccountFieldsetFactory。我错过了什么?
问题是由一个简单的疏忽引起的:AccountsTableFieldset 直接在构造函数中添加了 AccountFieldsets 的集合。这应该由 init() 方法完成。所以改变我的'AccountTableFieldset.php':
namespace Project\Form;
use Project\Model\AccountsTableModel;
use Zend\Form\Fieldset;
use Zend\Hydrator\ClassMethods;
use Zend\InputFilter\InputFilterProviderInterface;
class AccountsTableFieldset extends Fieldset implements InputFilterProviderInterface {
const NAME_ACCOUNTS = 'accounts';
public function __construct($name = null, $options = []) {
$name = isset ( $name ) ? $name : 'accounts-table';
parent::__construct ( $name, $options );
$this->setHydrator ( new ClassMethods ( false ) )->setObject ( new AccountsTableModel () );
}
public function init() {
$this->add ( [
'type' => 'Zend\Form\Element\Collection',
'name' => 'accounts',
'options' => [
'label' => 'Accounts',
'count' => 1,
'should_create_template' => false,
'allow_add' => false,
'target_element' => [
'type' => AccountFieldset::class,
]
]
] );
}
public function getInputFilterSpecification() {
return [
/** some InputFilterSpecification **/
];
}
}
以下场景:
我想通过 Zend Framework 3 表单编辑由 3 个字段(id、用户名和域)组成的 table 'accounts'。可以从一组域名中选择字段'domain'(这里是静态数组以简化事情)
我有一个带有 getter 和 setter 的简单实体 'AccountModel.php':
namespace Project\Model;
class AccountModel {
private $id;
private $userName;
private $domain;
public function getUserName(){
return $this->userName;
}
public function setUserName(string $userName){
$this->userName = $userName;
return $this;
}
public function getId() {
return $this->id;
}
public function setId(int $id) {
$this->id = $id;
return $this;
}
public function getDomain() {
return $this->domain;
}
public function setDomain($domain) {
$this->domain = $domain;
return $this;
}
}
和相应的字段集'AccountFieldset.php':
namespace Project\Form;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Form\Element\Text;
use Zend\Form\Element\Select;
use Project\Model\AccountModel;
use Zend\Hydrator\ClassMethods;
class AccountFieldset extends Fieldset implements
InputFilterProviderInterface {
const NAME_ID = 'id';
const NAME_USERNAME = 'username';
const NAME_DOMAIN = 'domain';
private $domainsOptionValues = [ ];
public function __construct(array $domains, $name = null, $options = null) {
parent::__construct ( isset ( $name ) ? $name : 'account-fieldset', $options );
$this->setHydrator ( new ClassMethods ( false ) )->setObject ( new AccountModel () );
$this->domainsOptionValues = $domains;
}
public function init() {
$this->add ( [
'name' => self::NAME_ID,
'type' => Text::class,
'options' => [
'label' => 'Id'
],
'attributes' => [
'required' => 'required',
'readonly' => true
]
] );
$this->add ( [
'name' => self::NAME_USERNAME,
'type' => Text::class,
'options' => [
'label' => 'Username'
],
'attributes' => [
'required' => 'required'
]
] );
$this->add ( [
'name' => self::NAME_DOMAIN,
'type' => Select::class,
'options' => [
'label' => 'Domains',
'value_options' => $this->domainsOptionValues
],
'attributes' => [
'required' => 'required'
]
] );
}
public function getInputFilterSpecification() {
return [
/** some InputFilterSpecifications **/
];
}
}
这个 Fieldset 将在另一个 Fieldset 中使用,它表示 table 'AccountTableFieldset.php':
namespace Project\Form;
use Project\Model\AccountsTableModel;
use Zend\Form\Fieldset;
use Zend\Hydrator\ClassMethods;
use Zend\InputFilter\InputFilterProviderInterface;
class AccountsTableFieldset extends Fieldset implements InputFilterProviderInterface {
const NAME_ACCOUNTS = 'accounts';
public function __construct($name = null, $options = []) {
$name = isset ( $name ) ? $name : 'accounts-table';
parent::__construct ( $name, $options );
$this->setHydrator ( new ClassMethods ( false ) )->setObject ( new AccountsTableModel () );
$this->add ( [
'type' => 'Zend\Form\Element\Collection',
'name' => 'accounts',
'options' => [
'label' => 'Accounts',
'count' => 1,
'should_create_template' => false,
'allow_add' => false,
'target_element' => [
'type' => AccountFieldset::class,
]
]
] );
}
public function getInputFilterSpecification() {
return [
/** some InputFilterSpecification **/
];
}
}
“AccountsTableModel.php”没有什么特别之处:
namespace Project\Model;
class AccountsTableModel {
private $accounts = [];
/**
* @return AccountModel[]
*/
public function getAccounts() : array {
return $this->accounts;
}
public function setAccounts(array $accounts) {
$this->accounts = $accounts;
return $this;
}
}
所以我的表格看起来像这样'AccountForm.php':
namespace Project\Form;
use Zend\Form\Form;
use Zend\Form\Element\Submit;
class AccountsForm extends Form {
const NAME_TABLE = 'accounts-table';
const NAME_SUBMIT= 'accounts-save';
public function init(){
$this->setName('accounts-form');
$this->add([
'name' => self::NAME_TABLE,
'type' => AccountsTableFieldset::class,
'attributes' => [
'class' => 'accounts',
'id' => 'accounts-table',
],
]);
$this->add([
'name' => self::NAME_SUBMIT,
'type' => Submit::class,
'attributes' => [
'class' => 'btn btn-success',
'value' => 'Save accounts',
'id' => 'accounts-save',
],
]);
}
}
此表单通过“AccountsFormFactory.php”中的 FormElementManager 初始化:
namespace Project\Factory;
use Interop\Container\ContainerInterface;
use Project\Form\AccountsForm;
use Zend\ServiceManager\Factory\FactoryInterface;
class AccountsFormFactory implements FactoryInterface{
public function __invoke(ContainerInterface $container){
$accountsForm = $container->get('FormElementManager')->get(AccountsForm::class);
$accountsForm->init();
return $accountsForm;
}
}
为了填充 AccountFieldset 中的 Select-元素,我创建了 '** AccountFieldsetFactory.php**':
namespace Project\Factory;
use Interop\Container\ContainerInterface;
use Project\Form\AccountFieldset;
class AccountFieldsetFactory {
public function __invoke(ContainerInterface $container){
$domains = [
'1' => 'example1.com',
'2' => 'example2.com',
'3' => 'example3.com',
];
$accountsFieldset = new AccountFieldset($domains);
$accountsFieldset->init();
// die(__FILE__ . ' #'. __LINE__);
return $accountsFieldset;
}
}
注意我让它死了。但不幸的是,这条线永远不会到达,因为 ElementFactory 直接调用了 AccountFieldset。此时我得到错误:
Uncaught TypeError: Argument 1 passed to Project\Form\AccountFieldset::__construct() must be of the type array, string given, called in /var/www/html/zf3.local/vendor/zendframework/zend-form/src/ElementFactory.php on line 70
为什么调用 ElementFactory 而不是我的 AccountFieldsetFactory?我将 'form_elements' 的 'factories' 配置如下 'ConfigProvider.php':
namespace MailManager;
use Zend\Db\Adapter;
use Project\Factory\FormElementManagerDelegatorFactory;
class ConfigProvider {
public function __invoke(){
return [
'dependencies' => $this->getDependencies(),
'routes' => $this->getRoutes(),
'templates' => $this->getTemplates(),
'form_elements' => [
'factories' => [
Form\AccountFieldset::class => Factory\AccountFieldsetFactory::class,
]
]
];
}
public function getDependencies(){
return [
'factories' => [
Action\AccountsAction::class => Factory\AccountsActionFactory::class,
Repository\AccountsRepositoryInterface::class => Factory\AccountsRepositoryFactory::class,
Storage\AccountsStorageInterface::class => Factory\AccountsStorageFactory::class,
Form\AccountsForm::class => Factory\AccountsFormFactory::class,
Form\NewAccountForm::class => Factory\NewAccountFormFactory::class,
Adapter\AdapterInterface::class => Adapter\AdapterServiceFactory::class,
],
'delegators' => [
'FormElementManager' => [
FormElementManagerDelegatorFactory::class,
],
],
];
}
public function getRoutes(){
return [
/** some routes **/
];
}
public function getTemplates(){
return [
'paths' => [
/** some paths **/
],
];
}
}
感谢 Configure FormElementManager #387 中的建议,FormElementManagerDelegatorFactory 工作正常并且 AccountFieldsetFactory 显示在 FormElementManager 的工厂部分中。但遗憾的是(如前所述)从未调用过 AccountFieldsetFactory。我错过了什么?
问题是由一个简单的疏忽引起的:AccountsTableFieldset 直接在构造函数中添加了 AccountFieldsets 的集合。这应该由 init() 方法完成。所以改变我的'AccountTableFieldset.php':
namespace Project\Form;
use Project\Model\AccountsTableModel;
use Zend\Form\Fieldset;
use Zend\Hydrator\ClassMethods;
use Zend\InputFilter\InputFilterProviderInterface;
class AccountsTableFieldset extends Fieldset implements InputFilterProviderInterface {
const NAME_ACCOUNTS = 'accounts';
public function __construct($name = null, $options = []) {
$name = isset ( $name ) ? $name : 'accounts-table';
parent::__construct ( $name, $options );
$this->setHydrator ( new ClassMethods ( false ) )->setObject ( new AccountsTableModel () );
}
public function init() {
$this->add ( [
'type' => 'Zend\Form\Element\Collection',
'name' => 'accounts',
'options' => [
'label' => 'Accounts',
'count' => 1,
'should_create_template' => false,
'allow_add' => false,
'target_element' => [
'type' => AccountFieldset::class,
]
]
] );
}
public function getInputFilterSpecification() {
return [
/** some InputFilterSpecification **/
];
}
}