Api platoform ManyToMany unidirectional relation with JoinTable error: attribute must be an array string given - api-platform.com

So I have two entites :
NextInfusionMashStepWithoutGrainAdjunct
// [...]
/**
* #ORM\ManyToMany(targetEntity=WaterGrainRatio::class)
* #ORM\JoinTable(name="next_infusion_mash_step_without_grain_adjunct_water_grain_ratio",
* joinColumns={#ORM\JoinColumn(name="next_infusion_mash_step_without_grain_adjunct_water_grain_ratio_id", referencedColumnName="id", nullable=false)},
* inverseJoinColumns={#ORM\JoinColumn(name="water_grain_ratio_id", referencedColumnName="id", nullable=false)}
* )
* #Groups("post:nextInfusionStepWithoutGrainAdjunct")
*/
private ?WaterGrainRatio $waterGrainRatioId;
//[...]
And WaterGrainRatio
//[...]
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
//[...]
I have added an WaterGrainRatioId with id: 1
And when I try to add a NextInfusionStepWithoutGrainAdjunct with postman with this body:
{
"waterGrainRatioId": "/dashboard/water_grain_ratios/1",
"...": "..."
}
I got this error: "The type of the \"waterGrainRatioId\" attribute must be \"array\", \"string\" given.",
I can't figure out why The IRI is not understood and why it needs an array.
What I tried:
#[ApiProperty(readableLink: false, writableLink: false)] but it did not works.
EDIT: I figure it out with this post, I just have to pass an array of IRI like this:
"waterGrainRatioId": [
"/dashboard/water_grain_ratios/1",
"/dashboard/water_grain_ratios/2"
]

Like I said in my edit I solve my problem by passing an array of IRIs:
"waterGrainRatioId": [
"/dashboard/water_grain_ratios/1",
"/dashboard/water_grain_ratios/2"
]

Related

Symfony 5 - Doctrine - Index not created in custom ManyToOne relation

I've created two Entity, and i try to create a custom relation between them, without using the default syntax.
See :
/**
* #ORM\Entity(repositoryClass=LandRepository::class)
* #ORM\HasLifecycleCallbacks()
*/
class Land
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $libelle;
/**
* #ORM\Column(type="integer")
*/
private $uid;
/**
* #ORM\OneToMany(targetEntity=Ride::class, mappedBy="uidparent")
*/
private $rides;
}
/**
* #ORM\Entity(repositoryClass=RideRepository::class)
* #ORM\HasLifecycleCallbacks()
*/
class Ride
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $libelle;
/**
* #ORM\ManyToOne(targetEntity=Land::class, inversedBy="rides")
* #ORM\JoinColumn(name="uidparent", referencedColumnName="uid")
*/
private $uidparent;
}
Tables are correctly created, but the last instruction have an error.
In MySQL, i made some test, and i need to create an index on "uid" column in "land" table.
So, i changed the header of my class "Land" to force the index
/**
* #ORM\Entity(repositoryClass=LandRepository::class)
* #ORM\Table(name="land",indexes={#ORM\Index(columns={"uid"})})
* #ORM\HasLifecycleCallbacks()
*/
class Land
{
/ ... /
}
I don't understand why i need to specify this index creation.
I hope to have something like this :
(PS : I know i can use the classic syntax (using in my entity Ride a column auto named "land_id") but I want to understand and master this alternative syntax to manage future complex entities and associations)
Thanks !
Need to manually specify in Entity header annotation :
#ORM\Table(name="land",indexes={#ORM\Index(columns={"uid"})})

Store JSON data into TEXT mysql column with doctrine

I have an entity with one TEXT (MySQL) attributes
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Index;
use ApiPlatform\Core\Annotation\ApiProperty;
/**
* #ApiResource(
* attributes={},
* collectionOperations={
* "get"={},
* "post"={
* "access_control"="is_granted('ROLE_COMPANY')"
* },
* },
* itemOperations={
* "get"={},
* "put"={"access_control"="is_granted('ROLE_COMPANY')"},
* }
* )
* #ORM\Entity(
* repositoryClass="App\Repository\SettingRepository",
* )
* #ORM\Table(
* indexes={#Index(name="domain_idx", columns={"domain"})}
* )
*/
class Setting
{
/**
* #var Uuid
* #ApiProperty(identifier=true)
* #ORM\Id
* #ORM\Column(type="string")
* #ORM\GeneratedValue(strategy="NONE")
*/
private $identifier;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $data = array();
/**
* #ORM\Column(type="string", nullable=true)
*/
private $domain = array();
public function getData()
{
if($this->data == null) return array();
$data = unserialize($this->data);
return $data;
}
public function setData($data): self
{
$this->data = serialize($data);
return $this;
}
/**
* #return mixed
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* #param mixed $key
*/
public function setIdentifier($identifier): self
{
$this->identifier = $identifier;
return $this;
}
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain): self
{
$this->domain = $domain;
return $this;
}
}
If I try to invoke the service with the following parameter structure it works fine:
{
"data": "testData",
"identifier": "testIdentifier",
"domain": "domain1"
}
But If I would like to store an embedded JSON string, for example:
"data": {"temp": 123}
I receive the following error:
hydra:description": "The type of the \"data\" attribute must be \"string\", \"array\" given.",
I tried to convert the object into an string in the method setData. But this method will not be invoked. It seams, that the API-Platform detects the wrong type and throws the exception.
I found some comments, that it is necessary to decorate the property:
https://api-platform.com/docs/core/serialization/#decorating-a-serializer-and-adding-extra-data
Can anyone give me an example? It does not work!
Where is the right place to serialise and unserialise the property data?
Does anyone have an idea?
Kind regards
You need to set the column type to json in MySQL. It should behave as expected.
/**
* #var array Additional data describing the setting.
* #ORM\Column(type="json", nullable=true)
*/
private $data = null;
I think null is more consistent than an empty array, but that's your choice.

symfony - unique entity constraint with not mapped field

my goal is to validate an entity with a uniqueEntity constrainte on a field. This field is composed of 2 not mapped fields concatenated using a lifecycle callback PrePersist. The problem is that the validation does not occur and the system allows me to insert data into the database when it should not.
/**
* Recipe
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\RecipeRepository")
* #ORM\HasLifecycleCallbacks()
* #UniqueEntity(
* ignoreNull = false,
* fields={"amount"},
* message="Not valid"
* )
*/
class Recipe
{...}
...
/**
* #ORM\PrePersist()
*/
public function preSave()
{
$this->amount = $this->getAmountInteger() . '.' . $this->getAmountDecimal();
}
i have a entity with this definition and run fine, but your case is different because your set 'amount' in the preSave...
* #UniqueEntity(fields={"amount"}, message="Not valid")
* #ORM\Table(uniqueConstraints={#ORM\UniqueConstraint(name="unique_amount", columns={"amount"})})
And in the other entity i used
#ORM\Column(type="string", length=127, nullable=false, unique=TRUE)
The different is in the first case i use compuest key and define key in the fields={field1,field2}.. in the second example i use simple unique key.

Symfony2 - UniqueEntity no action

I have an entity named Test with two fields: id and name.
I would like to have the name as unique.
What I did:
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
...
/**
* Company\AppBundle\Entity\Test
*
* #ORM\Table(name="test")
* #UniqueEntity("name")
* #ORM\Entity(repositoryClass="Company\AppBundle\Entity\TestRepository")
*
*/
class Test
{
....
/**
* #var string$name
*
* #ORM\Column(name="name", type="string", length=200, nullable=false, unique=true)
*/
private $name;
....
In my controller, I am using:
if ($form->isValid()) {
....
But the validation goes through. Am I missing something?
The unique annotation is for doctrine, it passes it to the database level and the error gets thrown from there. It will not know that the entity exists until you try to save it. To do the checks in php you have to query for the unique attribute yourself and check if it exists...

Symfony2+Doctrine - Validating one-to-many collection of entities

I have a form to create a new entity. That entity has a collection of other entities that are also entered in that form.
I want to use the validation options of the entity in the collection to validate those entities but it does not work. The validation rules of the "main" entity (Person) are checked, but the validation rules of the entities in the addressList collection (Address) are not checked. When I input invalid information in the fields, the submitted form is successfully validated.
In this example, the annotation for street is not used on validation.
class Person
{
...
/**
* #ORM\OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "detach"})
*/
protected $addressList;
....
}
class Address
{
...
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="addressList")
* #ORM\JoinColumn(name="person_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $owner;
/**
* #ORM\Column(type="string", length=75)
* #Assert\MinLength(
* limit=3,
* message="Street must have atleast {{ limit }} characters."
* )
*/
protected $street;
...
}
How can I get the form to validate the supplied Address entities?
I had the same problem but was solved with:
/**
* #ORM\OneToMany(
* targetEntity="Entity",
* mappedBy="mappedEntity",
* cascade={"persist" , "remove"}
* )
* #Assert\Valid
*/
I use this:
use Symfony\Component\Validator\ExecutionContextInterface;
class Person
{
...
/**
* #ORM\OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "detach"})
*/
protected $addressList;
....
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context)
{
if (!$this->getAddressList()->count()) {
$context->addViolationAt(
'addressList',
'You must add at least one address',
array(),
null
);
}
}
}
http://symfony.com/doc/current/reference/constraints/Callback.html
Just add annotation assert like following
/**
* #Assert\Count(
* min = "1",
* minMessage = "You must specify at least one"
* )
* #Assert\Valid
*
*/
protected $name_of_collection_property;
You could also use the "Valid" constraint with the "All" constraint :
/**
* #ORM\OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "detach"})
* #Assert\All({
* #Assert\Valid
* })
*/
protected $addressList;

Resources