Symfony2 表单验证耗尽内存

Symfony2 form validation exhausted memory

我有一个复杂的表单,其中包含许多实体选择字段。当我不使用验证时,add/edit 没问题。但是当我在 /config/validation.yml 中启用表单验证时,我不能再 add/edit 了。 我检查了 php 个日志文件并发现了一些错误:

PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted (tried to allocate 32 bytes) in ..\example.com\vendor\doctrine\dbal\lib\Doctrine\DBAL\Connection.php on line 694
PHP Stack trace:
PHP   1. {main}() ..\example.com\web\app_dev.php:0
PHP   2. Symfony\Component\HttpKernel\Kernel->handle() ..\example.com\web\app_dev.php:29
PHP   3. Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle() ..\example.com\var\bootstrap.php.cache:2377
PHP   4. Symfony\Component\HttpKernel\HttpKernel->handle() ..\example.com\var\bootstrap.php.cache:3133
PHP   5. Symfony\Component\HttpKernel\HttpKernel->handleRaw() ..\example.com\var\bootstrap.php.cache:2984
PHP   6. call_user_func_array:{..\example.com\var\bootstrap.php.cache:3022}() ..\example.com\var\bootstrap.php.cache:3022
PHP   7. DefaultBundle\Controller\PropertyHouseController->createAction() ..\example.com\var\bootstrap.php.cache:3022
PHP   8. Symfony\Component\Form\Form->createView() ..\example.com\src\DefaultBundle\Controller\PropertyHouseController.php:86
PHP   9. Symfony\Component\Form\Form->createView() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:1061
PHP  10. Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeDataCollectorProxy->buildView() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:1058
PHP  11. Symfony\Component\Form\ResolvedFormType->buildView() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeDataCollectorProxy.php:111
PHP  12. Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeDataCollectorProxy->buildView() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\ResolvedFormType.php:159
PHP  13. Symfony\Component\Form\ResolvedFormType->buildView() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeDataCollectorProxy.php:111
PHP  14. Symfony\Component\Form\Extension\Core\Type\ChoiceType->buildView() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\ResolvedFormType.php:162
PHP  15. Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList->getPreferredViews() ..\example.com\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\Type\ChoiceType.php:101
PHP  16. Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList->load() ..\example.com\vendor\symfony\symfony\src\Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList.php:174
PHP  17. Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader->getEntities() ..\example.com\vendor\symfony\symfony\src\Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList.php:430
PHP  18. Doctrine\ORM\AbstractQuery->execute() ..\example.com\vendor\symfony\symfony\src\Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader.php:71
PHP  19. Doctrine\ORM\Query->_doExecute() ..\example.com\vendor\doctrine\orm\lib\Doctrine\ORM\AbstractQuery.php:794
PHP  20. Doctrine\ORM\Query\Exec\SingleSelectExecutor->execute() ..\example.com\vendor\doctrine\orm\lib\Doctrine\ORM\Query.php:286
PHP  21. Doctrine\DBAL\Connection->executeQuery() ..\example.com\vendor\doctrine\orm\lib\Doctrine\ORM\Query\Exec\SingleSelectExecutor.php:50
PHP  22. PDOStatement->execute() ..\example.com\vendor\doctrine\dbal\lib\Doctrine\DBAL\Connection.php:694

如何解决这个错误?感谢您的帮助。

我的表格很复杂

public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('beds', 'choice', [
    'choices' => [
        '1' => '1',
        '2' => '2',
        '3' => '3',
        '4' => '4',
        '5' => '5',
        '6' => '6',
        '7' => '7',
        '8' => '8',
        '9' => '9+',
    ],
    'placeholder' => '',
])
->add('baths', 'choice', [
    'choices' => [
        '1' => '1',
        '2' => '2',
        '3' => '3',
        '4' => '4',
        '5' => '5',
        '6' => '6',
        '7' => '7',
        '8' => '8',
        '9' => '9+',
    ],
    'placeholder' => '',
])
->add('storey', 'choice', [
    'choices' => [
        '1' => '1',
        '2' => '2',
        '3' => '3',
        '4' => '4',
        '5' => '5',
        '6' => '6',
        '7' => '7',
        '8' => '8',
        '9' => '9+',
    ],
    'placeholder' => '',
    'required' => false,
])
->add('title')
->add('body')
->add('password', 'repeated', [
    'type' => 'password',
    'invalid_message' => 'The password fields must match.',
    'options' => array('attr' => array('class' => 'password-field')),
    'required' => true,
    'first_options' => array('label' => 'Password'),
    'second_options' => array('label' => 'Repeat Password'),
])
->add('province', 'entity', [
    'class' => 'AppBundle:Province',
    'property' => 'name',
    'placeholder' => ''
])
->add('activity', 'entity', [
    'class' => 'AppBundle:Activity',
    'property' => 'name',
    'placeholder' => ''
])
->add('unit', 'entity', [
    'class' => 'AppBundle:Unit',
    'property' => 'name',
    'placeholder' => ''
])
->add('direction', 'entity', [
    'class' => 'AppBundle:Direction',
    'property' => 'name',
    'placeholder' => '',
    'required' => false
])
->add('legal', 'entity', [
    'class' => 'AppBundle:Legal',
    'property' => 'name',
    'placeholder' => '',
    'required' => false
])
->add('images', 'collection', [
    'type' => new PropertyImageType(),
    'allow_add' => true,
    'allow_delete' => true
])
->add('amenities', 'entity', [
    'class' => 'AppBundle:Amenity',
    'property' => 'name',
    'expanded' => true,
    'multiple' => true,
    'query_builder' => function (EntityRepository $repository) {
        return $repository->createQueryBuilder('a')
            ->from('AppBundle:Amenity', 'amenity')
            ->where('a.type = 1');
    }
]);

然后我使用 EventSubscriber 动态添加一些字段

$builder->addEventSubscriber(new AddDistrictFieldSubscriber())
    ->addEventSubscriber(new AddStreetFieldSubscriber())
    ->addEventSubscriber(new AddWardFieldSubscriber())
    ->addEventSubscriber(new AddProjectFieldSubscriber());

class AddDistrictFieldSubscriber implements EventSubscriberInterface
{

    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * array('eventName' => 'methodName')
     *  * array('eventName' => array('methodName', $priority))
     *  * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
     *
     * @return array The event names to listen to
     *
     * @api
     */
    public static function getSubscribedEvents()
    {
        return [
            FormEvents::PRE_SET_DATA => 'preSetData',
            FormEvents::PRE_SUBMIT => 'preSubmit',
        ];
    }

    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        $province = $data->getProvince();
        $districts = $province === null ? [] : $province->getDistricts();

        $form->add('district', 'entity', [
            'class' => 'AppBundle:District',
            'property' => 'name',
            'placeholder' => '',
            'choices' => $districts,
        ]);
    }

    public function preSubmit(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        $province = array_key_exists('province', $data) ? $data['province'] : null;

        $form->add('district', 'entity', [
            'class' => 'AppBundle:District',
            'property' => 'name',
            'placeholder' => '',
            'query_builder' => function (EntityRepository $repository) use ($province) {
                return $repository->createQueryBuilder('d')
                    ->from('AppBundle:District', 'district')
                    ->where('district.province = :province')
                    ->setParameter('province', $province);
            }
        ]);
    }
}

class AddStreetFieldSubscriber implements EventSubscriberInterface
{

    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * array('eventName' => 'methodName')
     *  * array('eventName' => array('methodName', $priority))
     *  * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
     *
     * @return array The event names to listen to
     *
     * @api
     */
    public static function getSubscribedEvents()
    {
        return [
            FormEvents::PRE_SET_DATA => 'preSetData',
            FormEvents::PRE_SUBMIT => 'preSubmit',
        ];
    }

    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        $district = $data->getDistrict();
        $streets = $district === null ? [] : $district->getStreets();

        $form->add('street', 'entity', [
            'class' => 'AppBundle:Street',
            'property' => 'name',
            'placeholder' => '',
            'choices' => $streets,
            'required' => false,
        ]);
    }

    public function preSubmit(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        $district = array_key_exists('district', $data) ? $data['district'] : null;

        $form->add('street', 'entity', [
            'class' => 'AppBundle:Street',
            'property' => 'name',
            'placeholder' => '',
            'query_builder' => function (EntityRepository $repository) use ($district) {
                return $repository->createQueryBuilder('s')
                    ->from('AppBundle:Street', 'street')
                    ->where('street.district = :district')
                    ->setParameter('district', $district);
            }
        ]);
    }
}

数据库表: - 省份:id - 名称(70 条记录) - 分区:id - province_id - 前缀 - 名称(700 条记录) - 街道:id - district_id - 前缀 - 名称(17000 条记录) - 病房:id - district_id - 前缀 - 姓名(11000 条记录) ...还有一些表

调试表单中的每个字段后,我终于在 preSubmit() 的查询生成器中发现了问题

$form->add('district', 'entity', [
    'class' => 'AppBundle:District',
    'property' => 'name',
    'placeholder' => '',
    'query_builder' => function (EntityRepository $repository) use ($province) {
        return $repository->createQueryBuilder('d')
            ->from('AppBundle:District', 'district')
            ->where('district.province = :province')
            ->setParameter('province', $province);
    }
]);

$form->add('street', 'entity', [
    'class' => 'AppBundle:Street',
    'property' => 'name',
    'placeholder' => '',
    'query_builder' => function (EntityRepository $repository) use ($district) {
        return $repository->createQueryBuilder('s')
            ->from('AppBundle:Street', 'street')
            ->where('street.district = :district')
            ->setParameter('district', $district);
    }
]);

我使用不同的别名,所以学说会在每个 table --> 耗尽内存中获取所有记录。 因此,只需在查询构建器中使用相同的别名或删除 from() ;)

$form->add('district', 'entity', [
    'class' => 'AppBundle:District',
    'property' => 'name',
    'placeholder' => '',
    'query_builder' => function (EntityRepository $repository) use ($province) {
        return $repository->createQueryBuilder('district')
            ->where('district.province = :province')
            ->setParameter('province', $province);
    }
]);