symfonysymfony-formsnested-formssymfony5multiple-forms

Could not determine access type for property "offerOrders" in class "App\Form\OrderType": Neither the property


the error is:

Could not determine access type for property "offerOrders" in class "App\Form\OrderType": Neither the property "offerOrders" nor one of the methods "addOfferOrder()"/"removeOfferOrder()", "setOfferOrders()", "offerOrders()", "__set()" or "__call()" exist and have public access in class "App\Form\OrderType"..

PhotoSession Class

    <?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\PhotoSessionRepository")
 */
class PhotoSession
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;


    /**
     * @ORM\OneToOne(targetEntity="App\Entity\Order", inversedBy="photoSession", cascade={"persist", "remove"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $purchaseOrder;

Order Class

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\OrderRepository")
 * @ORM\Table(name="`order`")
 */
class Order
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\OneToOne(targetEntity="App\Entity\PhotoSession", mappedBy="purchaseOrder", cascade={"persist", "remove"})
     */
    private $photoSession;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\OfferOrder", mappedBy="purchaseOrder", cascade={"persist"})
     */
    private $offerOrders;

    public function __construct()
    {
        $this->offerOrders = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getPhotoSession(): ?PhotoSession
    {
        return $this->photoSession;
    }

    public function setPhotoSession(PhotoSession $photoSession): self
    {
        $this->photoSession = $photoSession;

        // set the owning side of the relation if necessary
        if ($photoSession->getPurchaseOrder() !== $this) {
            $photoSession->setPurchaseOrder($this);
        }

        return $this;
    }

    /**
     * @return Collection|OfferOrder[]
     */
    public function getOfferOrders(): Collection
    {
        return $this->offerOrders;
    }

    public function addOfferOrder(OfferOrder $offerOrder): self
    {
        if (!$this->offerOrders->contains($offerOrder)) {
            $this->offerOrders[] = $offerOrder;
            $offerOrder->setPurchaseOrder($this);
        }

        return $this;
    }

    public function removeOfferOrder(OfferOrder $offerOrder): self
    {
        if ($this->offerOrders->contains($offerOrder)) {
            $this->offerOrders->removeElement($offerOrder);
            // set the owning side to null (unless already changed)
            if ($offerOrder->getPurchaseOrder() === $this) {
                $offerOrder->setPurchaseOrder(null);
            }
        }

        return $this;
    }
}

offerOrder Class

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\OfferOrderRepository")
 */
class OfferOrder
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Offer", inversedBy="offerOrders")
     * @ORM\JoinColumn(nullable=false)
     */
    private $offer;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="offerOrders")
     * @ORM\JoinColumn(nullable=false)
     */
    private $purchaseOrder;

    /**
     * @ORM\Column(type="float", nullable=true)
     */
    private $quantity;

    /**
     * @ORM\Column(type="float", nullable=true)
     */
    private $totalPriceHt;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getOffer(): ?Offer
    {
        return $this->offer;
    }

    public function setOffer(?Offer $offer): self
    {
        $this->offer = $offer;

        return $this;
    }

    public function getPurchaseOrder(): ?Order
    {
        return $this->purchaseOrder;
    }

    public function setPurchaseOrder(?Order $purchaseOrder): self
    {
        $this->purchaseOrder = $purchaseOrder;

        return $this;
    }

    public function getQuantity(): ?float
    {
        return $this->quantity;
    }

    public function setQuantity(?float $quantity): self
    {
        $this->quantity = $quantity;

        return $this;
    }

    public function getTotalPriceHt(): ?float
    {
        return $this->totalPriceHt;
    }

    public function setTotalPriceHt(?float $totalPriceHt): self
    {
        $this->totalPriceHt = $totalPriceHt;

        return $this;
    }
}

offer class

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\OfferRepository")
 */
class Offer
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="float")
     */
    private $price;

    /**
     * @var Tag
     * @ORM\ManyToOne(targetEntity="App\Entity\Tag")
     * @ORM\JoinColumn(nullable=false)
     */
    private $tag;

    /**
     * @var integer|null
     * @ORM\Column(type="integer")
     */
    private $version = 0;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\OfferOrder", mappedBy="offer")
     */
    private $offerOrders;

    public function __construct()
    {
        $this->offerOrders = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getPrice(): ?float
    {
        return $this->price;
    }

    public function setPrice(float $price): self
    {
        $this->price = $price;

        return $this;
    }

    /**
     * @return Tag|null
     */
    public function getTag(): ?Tag
    {
        return $this->tag;
    }

    /**
     * @param Tag $tag
     * @return Offer
     */
    public function setTag(Tag $tag): self
    {
        $this->tag = $tag;
        return $this;
    }

    /**
     * @return int|null
     */
    public function getVersion(): ?int
    {
        return $this->version;
    }

    /**
     * @param int|null $version
     * @return Offer
     */
    public function setVersion(?int $version): self
    {
        $this->version = $version;
        return $this;
    }

    /**
     * @return Collection|OfferOrder[]
     */
    public function getOfferOrders(): Collection
    {
        return $this->offerOrders;
    }

    public function addOfferOrder(OfferOrder $offerOrder): self
    {
        if (!$this->offerOrders->contains($offerOrder)) {
            $this->offerOrders[] = $offerOrder;
            $offerOrder->setOffer($this);
        }

        return $this;
    }

    public function removeOfferOrder(OfferOrder $offerOrder): self
    {
        if ($this->offerOrders->contains($offerOrder)) {
            $this->offerOrders->removeElement($offerOrder);
            // set the owning side to null (unless already changed)
            if ($offerOrder->getOffer() === $this) {
                $offerOrder->setOffer(null);
            }
        }

        return $this;
    }
}

SessionPhotoType

    <?php

namespace App\Form;

use App\Entity\Family;
use App\Entity\PhotoSession;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PhotoSessionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('description')
            ->add('family', EntityType::class, [
                'class' => Family::class,
                'choice_label' => 'name',
            ])
            ->add('purchaseOrder', OrderType::class, [
                'data_class' => OrderType::class
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => PhotoSession::class,
        ]);
    }
}

OrderType

<?php

namespace App\Form;

use App\Entity\Order;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class OrderType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('offerOrders', CollectionType::class, [
                'entry_type' => OfferOrderType::class,
                'by_reference' => false,
                'allow_add' => true,
                'allow_delete' => true,
            ])
            ->add('totalPriceHt')
            ->add('tva')
            ->add('finalPrice')
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Order::class,
        ]);
    }
}

OfferOrderType

class OfferOrderType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder

        ->add('offer', EntityType::class, [
            'class' => Offer::class,
            'choice_label' => function(Offer $offer) {
                return sprintf('%s %f €', $offer->getTag()->getName(), $offer->getPrice());
            },
            'placeholder' => 'Choissiez une offre'
        ])
        ->add('quantity', NumberType::class)
        ->add('totalPriceHt', NumberType::class);
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'widget' => 'single_text',
        'data_class' => OfferOrder::class,
    ]);
}

}

I'm sorry for the long code blocks, but for the people who will try to solve the bug I think that there is the necessary one. The error is returned when I submit the form. The question I ask myself is why symfony is looking for accessors in the App\Form\OrderType class?

I put dumps everywhere in PropertyAccessor.php PropertyPathMapper.php and vendor/symfony/form/Form.php, and when it goes here:

if (FormUtil::isEmpty($viewData)) {
    $emptyData = $this->config->getEmptyData();

    if ($emptyData instanceof \Closure) {
        $emptyData = $emptyData($this, $viewData);
    }

    $viewData = $emptyData;
    dump($viewData);
 }

in Form.php it sets $viewData to App\Form\OrderType but I don't know why

The answer was:

For the form to pass, it was necessary to change purchaseOrder in photoSessionType like this:

-> add ('purchaseOrder', OrderType :: class)

Thanks again to @Ihor Kostrov


Solution

  • Try to change

    ->add('purchaseOrder', OrderType::class, [
                    'data_class' => OrderType::class
                ])
    

    To

    ->add('purchaseOrder', OrderType::class, [
                    'data_class' => Order::class
                ])
    

    Or just remove data_class option