ZF3 - 如何在一组场景集中填充选择元素

时间:2018-01-04 14:24:04

标签: php forms factory fieldset zend-framework3

以下情况:
我想通过Zend Framework 3表单编辑由'3个字段(id,username和domain)组成的表'accounts'。可以从一组域名中选择字段“域”(这里是一个简化事物的静态数组)

我有一个简单的实体,包含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;
    }
}

相应的Fieldset' 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 **/
        ];
    }
}

此字段集将用于代表表' 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-Element,我创建了'** 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'的'工厂'配置如下' 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。我错过了什么?

1 个答案:

答案 0 :(得分:0)

问题是由一个简单的疏忽引起的: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 **/
        ];
    }
}