I have a complex form that maps to a collection of Entity
, that permits to buy ticket at a given pricing for an event, for most of the events it works, but for one of them, we got in a repeatable fashion the error Could not determine access type for property "id"
In such a case I know that Could not determine access type for property X
is because a setter is missing. There's indeed no setId()
method and id
is protected
, but I think symfony should not try to set the id in the first place (because it works for the other forms, i.e the tickets are bought, and appears correctly link to the event etc.)
So my question is why in some case symfony REQUIRES the setId()
I have the following entities
class OrderFront
{
use Traits\HasId;
/**
* List of pricings set on this event
*
* @Assert\Valid()
* @ORM\OneToMany(
* targetEntity="QuantityByPricing",
* mappedBy="orderFront",
* cascade={"persist", "remove"}
* )
*/
private $quantitiesByPricing;
/**
* @Assert\NotBlank()
* @Assert\NotNull()
*/
public $occurenceId;
public function getQuantitiesByPricing(): Collection
{
return $this->quantitiesByPricing;
}
public function addQuantitiesByPricing(QuantityByPricing $quantityByPricing)
{
$quantityByPricing->setOrderFront($this);
$this->quantitiesByPricing[] = $quantityByPricing;
return $this;
}
}
class QuantityByPricing
{
use Traits\HasId;
/**
* @var int
* @ORM\Column(type="integer")
*/
public $quantity = 0;
/**
* The pricing of this ticket
*
* @var Pricing
*
* @ORM\ManyToOne(targetEntity="Pricing")
* @ORM\JoinColumn(
* name="pricing_id",
* nullable=false,
* referencedColumnName="id"
* )
*/
public $pricing;
}
Indeed the trait "HasId" has no setter (but it's on purpose) or at least until now it has never been a problem
trait HasId
{
/**
*
* @ORM\GeneratedValue(strategy="UUID")
* @ORM\Column(name="id", type="guid") }
* @ORM\Id
*
* @Assert\Uuid()
*/
private $id;
/**
* Get id
*
* @return guid
*/
public function getId()
{
return $this->id;
}
}
And the forms
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'quantitiesByPricing',
CollectionType::class,
['entry_type' => QuantityByPricingType::class]
)
->add('occurenceId', HiddenType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => 'AppBundle\Entity\OrderFront']);
}
}
/**
* sub-form to buy X tickets of a given pricing
*/
class QuantityByPricingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('quantity')
->add('pricing',HiddenPricingType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => 'AppBundle\Entity\QuantityByPricing']);
}
}
/**
*
*/
class HiddenPricingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('id', HiddenType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => 'AppBundle\Entity\Pricing']);
}
}
class Pricing
{
use Traits\HasId;
}
The controller creating the form looks like this
// call the DB to set the possible pricings
// inside it calls addQuantityByPricing
$orderFront = $this->_createOrderFront();
$form = $this->createForm(OrderType::class, $orderFront);
$form->handleRequest($request);
The exception traceback is the following
Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException:
Could not determine access type for property "id".
at vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:652
at Symfony\Component\PropertyAccess\PropertyAccessor->writeProperty(array(object(Pricing), object(Pricing)), 'id', '80424145-ca68-4dce-b4f0-644a423d3aad')
And when adding some debug
I can see that the 2 Pricing
are
array:2 [▼
0 => Pricing {#1375 ▶} # id "d82cafb8-432b-4e20-ac9f-66e48dc55458"
1 => & Pricing {#1375 ▶} # id "d82cafb8-432b-4e20-ac9f-66e48dc55458"
]
So it seems that symfony is trying to override the id of this pricing (which is a valid/existing one) by an other valid/existing one, which is I think the reason why it try to "replace" it, by trying to call the setter and fails, but why does it do so ?
Edit:
After some more debugging I found a troubling coincidence:
-
d82cafb8-432b-4e20-ac9f-66e48dc55458
is the first id to add when i added some debug in theaddQuantitiesByPricing
method -
80424145-ca68-4dce-b4f0-644a423d3aad
is the pricing id with the index 0 in the submitted form
Edit2: the way my controller create the form (and which is the one calling the addQuantitiesByPricing
making d82cafb8-432b-4e20-ac9f-66e48dc55458
to appear first ), make that we first retrieve these Ids from the database BEFORE taking the Ids POSTed