ZF2 + Doctrine2 + DoctrineObject Hydrator:ManyToOne关系的空值

时间:2013-04-17 13:29:14

标签: doctrine-orm zend-framework2 zend-form

对于我的项目中的类别结构,我正在构建一个实体。对于添加表单,我使用DoctrineObject水合器。当$ parent的值存在时,这可以正常工作,但是如果没有父项,它会给我一个错误,因为没有id可以选择父项。在这种情况下,parent属性的值应为null。

我会创建一个过滤器来执行此操作。执行此过滤器但是水合器似乎没有达到我想要的效果。

有人知道如何解决这个问题吗?

我的实体:     

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Flex\Entity\Entity;

/**
 * @Gedmo\Tree(type="materializedPath")
 * @ORM\Table(name="categories")
 * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\MaterializedPathRepository")
 */
class Category extends Entity
{
    /**
     * @ORM\OneToMany(mappedBy="parent", targetEntity="FlexCategories\Entity\Category")
     */
    protected $children;

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @Gedmo\TreeLevel
     * @ORM\Column(nullable=true, type="integer")
     */
    protected $level;

    /**
     * @ORM\Column(length=64, type="string")
     */
    protected $name;

    /**
     * @Gedmo\TreeParent
     * @ORM\ManyToOne(inversedBy="children", targetEntity="FlexCategories\Entity\Category")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(onDelete="SET NULL", referencedColumnName="id")
     * })
     */
    protected $parent;

    /**
     * @Gedmo\TreePath(appendId=false, endsWithSeparator=false, separator="/", startsWithSeparator=true)
     * @ORM\Column(length=255, nullable=true, type="string", unique=true)
     */
    protected $path;

    /**
     * @Gedmo\Slug(fields={"name"}, unique=false)
     * @Gedmo\TreePathSource
     * @ORM\Column(length=64)
     */
    protected $slug;

    public function setId($value)
    {
        $this->id = $value;
    }    
    public function setName($value)
    {
        $this->name = $value;
    }
    public function setParent($value)
    {
        $this->parent = $value;
    }
}

我的表格:     

use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;

class CategoryForm extends Form implements InputFilterProviderInterface, ServiceManagerAwareInterface
{
    private $_serviceManager;

    public function init()
    {
        // Init hydrator
        $hydrator        = new DoctrineObject($this->_serviceManager->get('doctrine.entitymanager.orm_default'),
                                              'FlexCategories\Entity\Category');

        // Set form basic configurations
        $this->setAttribute('method', 'post')
             ->setHydrator($hydrator);

        // Add parent field
        $this->add(array(
            'name'        => 'parent',
            'type'        => 'Zend\Form\Element\Hidden',
        ));

        // Add name field
        $this->add(array(
            'attributes'  => array(
                'required' => 'required',
            ),
            'name'        => 'name',
            'options'     => array(
                'label'       => 'Name',
            ),
            'type'        => 'Zend\Form\Element\Text',
        ));

        // Add description field
        $this->add(array(
            'name'       => 'description',
            'options'    => array(
                'label'       => 'Description',
            ),
            'type'       => 'Zend\Form\Element\Textarea',
        ));

        // Add CSRF element
        $this->add(array(
            'name'        => 'csrf',
            'type'        => 'Zend\Form\Element\Csrf',
        ));

        // Add submit button
        $this->add(array(
            'attributes'  => array(
                'type'    => 'submit',
                'value'   => 'Save',
            ),
            'name'        => 'submit',
        ));
    }
    public function getInputFilterSpecification()
    {
        return array(
            'description'  => array(
                'filters'     => array(
                    array(
                        'name'    => 'Zend\Filter\StringTrim'
                    ),
                ),
                'required'    => false,
            ),
            'name'         => array(
                'filters'     => array(
                    array(
                        'name'    => 'Zend\Filter\StringTrim'
                    ),
                ),
                'required'    => true,
                'validators'  => array(
                    array(
                        'name'    => 'Flex\Validator\EntityUnique',
                        'options' => array(
                            'entity'    => 'FlexCategories\Entity\Category',
                            'filter'   => array(
                                array('property'     => 'parent',
                                      'value'        => array('_context', 'parent')),
                            ),
                            'property'  => 'name',
                            'serviceLocator'    => $this->_serviceManager,
                        ),
                    ),
                ),
            ),
            'parent'       => array(
                'filters'     => array(
                    array(
                        'name'    => 'Flex\Filter\NullIfEmpty'
                    ),
                ),
                'required'    => false,
            ),
        );
    }

    public function setServiceManager(ServiceManager $serviceManager)
    {
        $this->_serviceManager = $serviceManager;
        $this->init();

        return $this;
    }
}

我的控制器:     

use Flex\Controller\AbstractController;
use FlexCategories\Entity\Category;
use FlexCategories\Form\CategoryForm;

class AdminController extends AbstractController
{
    public function addAction()
    {
        // Load form
        $form                = $this->getServiceLocator()->get('FlexCategories\Form\CategoryForm');

        // Create and bind new entity
        $category            = new Category();
        $form->bind($category);

        // Load parent category if present
        $parentId            = $this->params()->fromRoute('id', null);
        if ($parentId !== null)
        {
            if (!is_numeric($parentId))
                throw new \InvalidArgumentException('Invalid parent id specified');

            $entityManager   = $this->getEntityManager();
            $repository      = $entityManager->getRepository('FlexCategories\Entity\Category');
            $parent          = $repository->find($parentId);

            if (!$parent)
                throw new \InvalidArgumentException('Invalid parent id specified');

            $form->get('parent')->setValue($parent->getId());
        }

        // Process request
        $request             = $this->getRequest();
        if ($request->isPost())
        {
            $form->setData($request->getPost());

            if ($form->isValid())
            {                
                $entityManager    = $this->getEntityManager();
                $entityManager->persist($category);
                $entityManager->flush();

                $this->flashMessenger()->addSuccessMessage(sprintf('The category "%1$s" has succesfully been added.', $category->getName()));
                return $this->redirect()->toRoute($this->getEvent()->getRouteMatch()->getMatchedRouteName());
            }
        }

        // Return form
        return array(
            'form'           => $form,
        );
    }
    public function indexAction()
    {
        // Load all categories
        $entityManager        = $this->getEntityManager();
        $repository           = $entityManager->getRepository('FlexCategories\Entity\Category');
        $categories           = $repository->findBy(array(), array('path' => 'asc'));

        return array(
            'categories'      => $categories,
        );
    }
}

我的数据库:

CREATE TABLE `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) DEFAULT NULL,
  `level` int(11) DEFAULT NULL,
  `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `path` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `slug` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQ_3AF34668B548B0F` (`path`),
  KEY `IDX_3AF34668727ACA70` (`parent_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


ALTER TABLE `categories`
  ADD CONSTRAINT `FK_3AF34668727ACA70` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL;

2 个答案:

答案 0 :(得分:0)

我通过创建一个基于“DoctrineModule \ Form \ ElementObjectSelect”的“HiddenElement”元素来解决这个问题,并将其用作输入类型。

答案 1 :(得分:0)

您需要一个处理''值的策略。 (''!= null)因为ocramius指出它可能有''。

的主键

你使用“empty_option”时遇到了这个问题,目前你只能将null设置为null,表单帖子将始终转移''。

请参阅https://github.com/doctrine/DoctrineModule/pull/119

请参阅https://github.com/doctrine/DoctrineModule/pull/106

所以将此字段的策略添加到水化器中以将''转换为null。

这可能看起来像:

use Zend\Stdlib\Hydrator\Strategy\DefaultStrategy;

class ForeignKey extends DefaultStrategy
{
    public function hydrate($value)
    {
        if($value == '') {
            return NULL;
        }

        return $value;
    }
}