具有多对一的表单类型会导致其他字段错误(空)


formtype with ManyToOne cause other field error(null)

我正在尝试创建以下结构,但得到 1 个让我发疯的错误。

一个客户

有多张发票,一张发票属于一个客户

我使用Symfony2表单类型,学说ORM实体,

CRUD 在发票实体中没有一对多的情况下工作正常,但在添加一对多关系后,然后创建和更新总是抛出空错误

数据库结构:

client - id, username, email, [.....]
invoice - id, client_id, total, [.....]

"发票表client_id"字段具有对客户端表 ID 字段的外键引用

控制器:

    public function createAction($id, Request $request) {
        $form = $this->createCreateForm(new Invoice, $id);
        $form->handleRequest($request);
        if ($form->isValid()) {
            $em = $this->get('doctrine')->getManager();
            $em->persist($form->getData());
            $em->flush();
            $this->addFlash('success', 'Invoice is created.');
            return $this->redirectToRoute('client_invoice_list', array('id' => $id));
        } else {
            $errors = (string)$form->getErrors(true);
            $form->addError(new FormError($errors));
        }
        return $this->render('WebulousEcocrmBundle:Invoice:create.html.twig', array(
                    '_action' => 'clients',
                    'form' => $form->createView()
        ));
    }
    public function createFormAction($id, Request $request) {
        $client = $this->findClientById($id);
        if (null == $client) {
            return $this->handleNotFound();
        }
        $invoice = new Invoice();
        $form = $this->createCreateForm($invoice, $id);
        return $this->render('WebulousEcocrmBundle:Invoice:create.html.twig', array(
                    '_action' => 'clients',
                    'form' => $form->createView()
        ));
    }
    private function createCreateForm(Invoice $invoice, $id) {
        $form = $this->createForm(new InvoiceType, $invoice, array(
            'method' => 'POST',
            'action' => $this->generateUrl('client_invoice_create_form', array('id' => $id)),
        ));
        // here is the hidden input to store the client_id
        // work fine without relationship, but after added relationship it like ignore this
        $form->add('clientId', 'hidden', array('data' => $id));
        $form->add('stat', 'hidden', array('data' => 0));
        $form->add('submit', 'submit');
        return $form;
    }

这是发票实体

<?php
namespace Webulous'EcocrmBundle'Entity;
use Doctrine'ORM'Mapping as ORM;
/**
 * Invoice
 *
 * @ORM'Table()
 * @ORM'Entity(repositoryClass="Webulous'EcocrmBundle'Entity'InvoiceRepository")
 */
class Invoice
{
    const STATUS_UNPAID = 0;
    const STATUS_PAID = 1;
    const STATUS_CANCELLED = 2;
    const TAX_CHARGE_PERCENTAGE = 1;
    const TAX_CHARGE_FIXED = 2;
    /**
     * @var integer
     *
     * @ORM'Column(name="id", type="integer")
     * @ORM'Id
     * @ORM'GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @ORM'ManyToOne(targetEntity="Client", inversedBy="invoice")
     * @ORM'JoinColumn(name="client_id", referencedColumnName="id")
     */
    protected $client;
    /**
     * @var integer
     *
     * @ORM'Column(name="client_id", type="integer")
     */
    private $clientId;
    /**
     * @var integer
     *
     * @ORM'Column(name="stat", type="smallint")
     */
    private $stat;
    [.......]
    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
    public function getInvoiceId() {
        return 'MPN#' . str_pad($this->id, 6, '0', STR_PAD_LEFT);
    }
    /**
     * Set clientId
     *
     * @param integer $clientId
     * @return Invoice
     */
    public function setClientId($clientId)
    {
        $this->clientId = $clientId;
        return $this;
    }
    /**
     * Get clientId
     *
     * @return integer 
     */
    public function getClientId()
    {
        return $this->clientId;
    }
    /**
     * Set stat
     *
     * @param integer $stat
     * @return Invoice
     */
    public function setStat($stat)
    {
        $this->stat = $stat;
        return $this;
    }
    /**
     * Get stat
     *
     * @return integer 
     */
    public function getStat()
    {
        return $this->stat;
    }
    [........]
    public function getStatExplain() {
        return $this->transformStat($this->getStat());
    }
    private function transformStat($value) {
        switch ($value) {
            case self::STATUS_UNPAID:
                return 'Unpaid';
            case self::STATUS_PAID:
                return 'Paid';
            case self::STATUS_CANCELLED:
                return 'Cancelled';
            default:
                return 'Unpaid';
        }
    }
    private function reverseTransformStat($value) {
        switch ($value) {
            case 'Unpaid':
                return self::STATUS_UNPAID;
            case 'Paid':
                return self::STATUS_PAID;
            case 'Cancelled':
                return self::STATUS_CANCELLED;
            default:
                return self::STATUS_UNPAID;
        }
    }
    /** @ORM'PrePersist */
    public function prePresist() {
        $this->created = time();
        $this->updated = time();
    }
    /** @ORM'PreUpdate */
    public function preUpdate() {
        $this->updated = time();
    }
    /**
     * Set client
     *
     * @param 'Webulous'EcocrmBundle'Entity'Client $client
     * @return Invoice
     */
    public function setClient('Webulous'EcocrmBundle'Entity'Client $client = null)
    {
        $this->client = $client;
        return $this;
    }
    /**
     * Get client
     *
     * @return 'Webulous'EcocrmBundle'Entity'Client 
     */
    public function getClient()
    {
        return $this->client;
    }
    /**
     * Set total
     *
     * @param integer $total
     * @return Invoice
     */
    public function setTotal($total) {
        $this->total = $total;
        return $this;
    }
    /**
     * Get stat
     *
     * @return integer 
     */
    public function getTotal() {
        //TODO: sum onetomany
        return '1000';
        // return $this->total;
    }
}

这是客户端实体

<?php
namespace Webulous'EcocrmBundle'Entity;
use Doctrine'ORM'Mapping as ORM;
use Doctrine'Common'Collections'ArrayCollection;
use Symfony'Bridge'Doctrine'Validator'Constraints'UniqueEntity;
/**
 * @ORM'Entity
 * @ORM'Table(name="client")
 * @ORM'HasLifecycleCallbacks()
 * @UniqueEntity(fields="username", message="Username is not available.")
 */
class Client
{
    /**
     * @ORM'Column(type="integer")
     * @ORM'Id
     * @ORM'GeneratedValue(strategy="AUTO")
     */
    protected $id;
    /**
     * @ORM'OneToMany(targetEntity="Invoice", mappedBy="client")
     */
    protected $invoice;
    /**
     * @ORM'Column(type="string", length=60, unique=true)
     */
    protected $username;
   [.......]
    /**
     * @ORM'ManyToMany(targetEntity="Module")
     * @ORM'JoinTable(name="clients_modules",
     *      joinColumns={@ORM'JoinColumn(name="client_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM'JoinColumn(name="module_id", referencedColumnName="id")}
     *      )
     **/
    protected $modules;
    /**
     * @ORM'ManyToMany(targetEntity="Transaction")
     * @ORM'JoinTable(name="clients_transactions",
     *      joinColumns={@ORM'JoinColumn(name="client_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM'JoinColumn(name="transaction_id", referencedColumnName="id")}
     *      )
     **/
    protected $transactions;
    /**
     * @ORM'ManyToOne(targetEntity="Package", inversedBy="clients")
     * @ORM'JoinColumn(name="package_id", referencedColumnName="id", nullable=true)
     */
    protected $package;
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->modules = new ArrayCollection();
        $this->invoice = new ArrayCollection();
    }
    /**
     * @ORM'PrePersist
     */
    public function prePersist()
    {
        $this->created = time();
    }
    /**
     * @ORM'PreUpdate
     */
    public function preUpdate()
    {
        $this->updated = time();
    }
    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }
    [......]
    /**
     * Add modules
     *
     * @param 'Webulous'EcocrmBundle'Entity'Module $modules
     * @return Client
     */
    public function addModule('Webulous'EcocrmBundle'Entity'Module $modules)
    {
        $this->modules[] = $modules;
        return $this;
    }
    /**
     * Remove modules
     *
     * @param 'Webulous'EcocrmBundle'Entity'Module $modules
     */
    public function removeModule('Webulous'EcocrmBundle'Entity'Module $modules)
    {
        $this->modules->removeElement($modules);
    }
    /**
     * Get modules
     *
     * @return 'Doctrine'Common'Collections'Collection
     */
    public function getModules()
    {
        return $this->modules;
    }
    /**
     * Add transactions
     *
     * @param 'Webulous'EcocrmBundle'Entity'Transaction $transactions
     * @return Client
     */
    public function addTransaction('Webulous'EcocrmBundle'Entity'Transaction $transactions)
    {
        $this->transactions[] = $transactions;
        return $this;
    }
    /**
     * Remove transactions
     *
     * @param 'Webulous'EcocrmBundle'Entity'Transaction $transactions
     */
    public function removeTransaction('Webulous'EcocrmBundle'Entity'Transaction $transactions)
    {
        $this->transactions->removeElement($transactions);
    }
    /**
     * Get transactions
     *
     * @return 'Doctrine'Common'Collections'Collection
     */
    public function getTransactions()
    {
        return $this->transactions;
    }
    /**
     * Set active
     *
     * @param boolean $active
     * @return Client
     */
    public function setActive($active)
    {
        $this->active = $active;
        return $this;
    }
    /**
     * Get active
     *
     * @return boolean
     */
    public function isActive()
    {
        return $this->active;
    }
    public function setAaFormNo($val)
    {
        $this->aaFormNo = $val;
        return $this;
    }
    public function getAaFormNo()
    {
        return $this->aaFormNo;
    }
    public function setExpireDate($val)
    {
        $this->expireDate = $val;
        return $this;
    }
    public function getExpireDate()
    {
        return $this->expireDate;
    }
    public function setPackage(Package $package = null)
    {
        $this->package = $package;
        return $this;
    }
    public function getPackage()
    {
        return $this->package;
    }
    /**
     * Get active
     *
     * @return boolean 
     */
    public function getActive()
    {
        return $this->active;
    }
    /**
     * Add invoice
     *
     * @param 'Webulous'EcocrmBundle'Entity'Invoice $invoice
     * @return Client
     */
    public function addInvoice('Webulous'EcocrmBundle'Entity'Invoice $invoice)
    {
        $this->invoice[] = $invoice;
        return $this;
    }
    /**
     * Remove invoice
     *
     * @param 'Webulous'EcocrmBundle'Entity'Invoice $invoice
     */
    public function removeInvoice('Webulous'EcocrmBundle'Entity'Invoice $invoice)
    {
        $this->invoice->removeElement($invoice);
    }
    /**
     * Get invoice
     *
     * @return 'Doctrine'Common'Collections'Collection 
     */
    public function getInvoice()
    {
        return $this->invoice;
    }
}

这是表单类型

<?php
namespace Webulous'EcocrmBundle'Form;
use Symfony'Component'Form'AbstractType;
use Symfony'Component'Form'FormBuilderInterface;
use Symfony'Component'OptionsResolver'OptionsResolverInterface;
class InvoiceType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('fromCompanyName', 'text', array('label' => 'From Company Name', 'data' => 'Default Company Name', 'required' => false))
            ->add('toCompanyName', 'textarea', array('label' => 'To Company Name', 'required' => false))
            ->add('fromCompanyAddress', 'textarea', array(
                'label' => 'From Company Address',
                'data' => 'From Company Address',
                'required' => false
                )
            )
            ->add('toCompanyAddress', 'textarea', array('label' => 'To Company Address', 'required' => false))
            ->add('fromContact', 'text', array('label' => 'From Contact Number', 'data' => '6011111111', 'required' => false))
            ->add('fromEmail', 'email', array('label' => 'From E-Mail', 'data' => 'admin@admin.com', 'required' => false))
            ->add('taxType', 'choice', array('label' => 'Tax Type', 'choices' => array(
                '1' => 'Charge by percentage',
                '2' => 'Charge a fixed cost'
            ), 'required' => false))
            ->add('tax', 'text', array('label' => 'Tax', 'required' => false))
            ->add('taxDescription', 'text', array('label' => 'Tax Description', 'required' => false))
        ;
    }
    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Webulous'EcocrmBundle'Entity'Invoice'
        ));
    }
    /**
     * @return string
     */
    public function getName()
    {
        return 'webulous_ecocrmbundle_invoice';
    }
}

这是错误消息

An exception occurred while executing 'INSERT INTO Invoice (client_id, stat, created, updated, from_company_name, to_company_name, from_company_address, to_company_address, from_contact, from_email, tax_type, tax, tax_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' with params [null, [.........]]
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'client_id' cannot be null

我不知道为什么client_id($clientId)会突然变为空,我使用浏览器检查检查元素隐藏的输入仍然存在

如我所见,发票实体有

/**
* @ORM'ManyToOne(targetEntity="Client", inversedBy="invoice")
* @ORM'JoinColumn(name="client_id", referencedColumnName="id")
*/
protected $client;
/**
* @var integer
*
* @ORM'Column(name="client_id", type="integer")
*/
private $clientId;

两个都瞄准了同一个领域client_id,我觉得这是错误原因,但我不知道为什么,也不知道如何解决,有什么想法吗? 许多人欣赏

我找到了一个解决方案,在持久化之前使用 setClient 并处理请求来设置客户端,然后问题就解决了,所以我也从发票实体中删除了 clientId,无需重复

public function createAction($id, Request $request) {
    $client = $this->findClientById($id);
    if (null == $client) {
        return $this->handleNotFound();
    }
    $invoice = new Invoice();
    $invoice->setClient($client);
    $invoice->setStat(0);
    $form = $this->createForm(new InvoiceType, $invoice, array(
        'method' => 'POST',
        'action' => $this->generateUrl('client_invoice_create_form', array('id' => $id)),
    ));
    $form->add('submit', 'submit');
    $form->handleRequest($request);
    if ($form->isValid()) {
        $em = $this->get('doctrine')->getManager();
        $em->persist($form->getData());
        $em->flush();
        $this->addFlash('success', 'Invoice is created.');
        return $this->redirectToRoute('client_invoice_list', array('id' => $id));
    } else {
        $errors = (string)$form->getErrors(true);
        $form->addError(new FormError($errors));
    }
    return $this->render('WebulousEcocrmBundle:Invoice:create.html.twig', array(
                '_action' => 'clients',
                'form' => $form->createView()
    ));
}