API-Platform, Error when doing POST. Assert trigger despite that the data is sent - api-platform.com

I have an error when I POST new data using API-Platform (Swagger/Postman). Somebody can explain?
I have a fixtures that works as expected.
curl -X POST "http://localhost:8080/countries" -H "accept: application/json" -H "Content-Type: application/json" -d "[{\"name\":\"Aruba2\",\"iso2\":\"ZZ\",\"iso3\":\"ZZZ\",\"region\":\"Americas\",\"subregion\":\"Caribbean\",\"capital\":\"Oranjestad\",\"latitude\":12.5,\"longitude\":-69.96666666,\"isEc\":false,\"isEfta\":false}]"
The Data is well structured and valid.
The Error I am receiving
{
"type": "https://tools.ietf.org/html/rfc2616#section-10",
"title": "An error occurred",
"detail": "name: The country name should not be blank\nname: The country name should not be null\niso2: The iso-2 cannot be blank\niso3: The iso-3 cannot be blank\nregion: The region cannot be blank\nsubregion: The sub-region cannot be blank\ncapital: The capital cannot be blank\nlatitude: The latitude cannot be blank\nlongitude: The longitude cannot be blank\nisEc: The field cannot be blank\nisEfta: The field cannot be blank",
"violations": [
{
This is the entity code.
/**
*
* #ApiResource(
* iri="https://schema.org/Country",
* routePrefix="/",
* shortName="Countries",
* description="API Access to Country Entity",
* normalizationContext={"groups"={"country:read"}},
* denormalizationContext={"groups"={"country:write"}},
* collectionOperations={"GET","POST",
* "GETLIST"={
* "method"="GET",
* "description"="Retrieves a limited set of properties of Country resources",
* "path"="country_list_short",
* "normalization_context"={"groups":"country:list:short:read"}
* }
* }
* )
*
* #ORM\Entity(repositoryClass=CountryRepository::class)
*
*/
class Country extends AbstractEntity
/**
*
* #ORM\Column(
* name="name",
* type="string",
* length=128,
* nullable=false,
* options={"comment":"Country name (English)"}
* )
*
* #Assert\Unique(message="The name {{ value }} already in the table")
* #Assert\NotBlank(message="The country name should not be blank")
* #Assert\NotNull(message="The country name should not be null")
* #Assert\Length(
* min="2",
* minMessage="The Country name expected minimum length is {{ limit }}",
* max="128",
* maxMessage="The Country name expected maximum length is {{ limit }}"
* )
*
* #Groups({"country:read","country:write","country:list:short:read"})
*/
private $name;

You are sending a JSON array instead of JSON object.
Just remove both prepended and appended squared brackets:
{\"name\":\"Aruba2\",\"iso2\":\"ZZ\",\"iso3\":\"ZZZ\",\"region\":\"Americas\",\"subregion\":\"Caribbean\",\"capital\":\"Oranjestad\",\"latitude\":12.5,\"longitude\":-69.96666666,\"isEc\":false,\"isEfta\":false}
EDIT:
Also you are using the #Assert\Unique annotation. This is a mistake. This annotation must be used with collections. Given the error message and your property type, you want to use #Assert\UniqueEntity`, which is a class contraint checking whether the property is unique within the table.

Related

Symfony Doctrine not working when uses "SEQUENCE" as GeneratedValue in Oracle

I have the following Entity in Symfony 2 with an ID generated by an Oracle Sequence:
namespace PSBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="CONTRATO")
* #ORM\Entity
* #ORM\Entity(repositoryClass="PSBundle\Entity\ContratoRepository")
*/
class Contrato
{
/**
* #var integer
*
* #ORM\Column(name="ID_CONTRATO", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="SID_CONTRATO", allocationSize=1, initialValue=1)
*/
private $idContrato;
// Other fields...
}
But when I do:
$entityContrato = new Contrato();
// Set some fields
$em->persist($entityContrato);
I get:
Entity of type PSBundle\Entity\Contrato is missing an assigned ID for
field 'idContrato'. The identifier generation strategy for this
entity requires the ID field to be populated before
EntityManager#persist() is called. If you want automatically generated
identifiers instead you need to adjust the metadata mapping
accordingly.
I know that SID_CONTRATO is well defined as if I select the NEXTVAL it works:
What I'm missing? Any kind of light in the subject would be more than appreciated, my related dependencies are:
"php": "7.2",
"symfony/symfony": "2.3.42",
"doctrine/orm": "2.3.x-dev",
"doctrine/doctrine-bundle": "1.2.0",
Doctrine config (from config/config.yml, I don't have a doctrine.yaml file):
# Doctrine Configuration
doctrine:
dbal:
driver: oci8
port: 1521
dbname: ORCL
host: %dbal_host%
user: %dbal_user%
password: %dbal_pass%
charset: UTF8
memory: true
logging: true
orm:
auto_generate_proxy_classes: %kernel.debug%
auto_mapping: true
# IMPORTANT: this fixes bugs with the date format that is brought from Oracle ('d/m/y')
# The problem is described here: https://stackoverflow.com/questions/37723240/symfony-doctrine-oracle-datetime-format-issue
services:
oracle.listener:
class: Doctrine\DBAL\Event\Listeners\OracleSessionInit
tags:
- { name: doctrine.event_listener, event: postConnect }
Ok, I found the problem:
If I do a var_dump() in doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php of $class->idGenerator it outputs:
object(Doctrine\ORM\Id\AssignedGenerator)
While if I do the same with entities that currently work with sequences they output:
object(Doctrine\ORM\Id\SequenceGenerator)...
So Symfony wasn't getting the correct type of Generator. I check the entity file and found out that there was some fields with #ORM\GeneratedValue(strategy="NONE"), eg.:
/**
* #var integer
*
* #ORM\Column(name="ID_CONTRATO", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="SID_CONTRATO", allocationSize=1, initialValue=1)
*/
private $idContrato;
/**
* #var string
*
* #ORM\Column(name="TIPO", type="string", length=1, nullable=false)
* #ORM\GeneratedValue(strategy="NONE") // This was the conflicting line
*/
private $tipo;
Apparently, Symfony gets the last #ORM\GeneratedValue definition as the class identifier generator, which in this case had the strategy="NONE" so It couldn't get the next value from the sequence.

(API-PLATFORM) How can I update one property of an entity at a time

How can I update a property of an entity?
I would like to update (add / remove) the address property of an organization entity. It is a relation (ManyToMany) with the address entity. An organization can have one or more addresses; at one address, there can be multiple organizations.
Here is an excerpt from my organizational entity.
...
* #ApiResource(
* iri="https://schema.org/Organization",
* routePrefix="/",
* shortName="Organisations",
* description="API Access to the Chafea Data Collector: Common : organisation Entity",
* collectionOperations={"GET", "POST"},
* itemOperations={"GET", "PUT", "PATCH"},
* attributes={
* "order"={
* "legalName",
* "acronym"
* }
* }
* )
...
class Organisation extends AbstractEntity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer", options={"comment":"Primary Key, Auto generated"})
*/
private $id;
...
/**
* #var address the address (not mandatory) of an organisation
*
* #ORM\ManyToMany(targetEntity=Address::class, inversedBy="organisations")
*
* #ORM\JoinColumn(nullable=true).
*/
private $address;
...
}
The JSON structure for the PUT operation (http://url-api/organisations/{id}).
{
"legalName": "string",
"acronym": "string",
"address": [
"string"
],
"createdAt": "2020-10-29T13:01:20.637Z",
"modifiedAt": "2020-10-29T13:01:20.637Z"
}
Do I have to retrieve the whole data set, update the IRIs of the address array and send back the updated data set (PUT)?
http.get(url);
...
data = {
"legalName": "My organisation",
"acronym": "MYORG",
"address": [],
"createdAt": "2020-10-29T13:01:20.637Z",
"modifiedAt": "2020-10-29T13:01:20.637Z"
}
...
data.address": ['/addresses/1', '/addresses/5']
...
http.put(url,data,headers);
Maybe do I have to create a dedicated PUT operation for the address only?
Thank you.
The issue is somewhere else, the ManyToMany relation is not working out of the box, it works one way (Address is creating the relation with Person, but Person creating the relation with Address)...

API Platform - Invalid value provided (invalid IRI?). How to POST with relations?

I setup a pretty straightforward Group -> GroupUser -> User relationship (GroupUser being a join table with additional data fields, Group OneToMany GroupUsers, which has ManyToOne Group and User, and User with OneToMany GroupUser) and fail to POST to the API, receiving an invalid IRI error. There seems to be quite some confusion about this, I found several posts (here and here) on this but none clearly addresses my most basic scenario (I followed the API platform documentation and have a Symfony4 with a require api-platform/api-pack installation).
I am using IRI to associate the objects, but the following POST does not work (I do have a user with id 1 and a group with id 1) and returns the Invalid value provided (invalid IRI?) error:
curl -X POST "http://api.platform.local/group_users" -H "accept: application/ld+json" -H "Content-Type: application/ld+json" -d "{ \"group\": \"/groups/1\", \"user\": \"/users/1\", \"role\": \"member\"}"
Here are my entities:
App/Entity/Group
/**
* #ORM\OneToMany(targetEntity="GroupUser", mappedBy="group")
*/
private $users;
App/Entity/User
/**
* #ORM\OneToMany(targetEntity="GroupUser", mappedBy="user")
*/
private $groups;
App/Entity/GroupUser
/**
* #Assert\NotBlank
* #ORM\JoinColumn(
* nullable=false,
* onDelete="CASCADE"
* )
* #ORM\ManyToOne(
* inversedBy="users",
* targetEntity="App\Entity\Group"
* )
*/
private $group;
/**
* #Assert\NotBlank
* #ORM\JoinColumn(
* nullable=false,
* onDelete="CASCADE"
* )
* #ORM\ManyToOne(
* inversedBy="groups",
* targetEntity="App\Entity\User"
* )
*/
private $user;
I must be missing something fundamental, but the documentation doesn't seem to require anything else. This post didn't help, I even added an id to the UserGroup entity just for that, initially I only had user and group as table ids.
Your help is appreciated, many thanks beforehand.
I finally figured this out, actually I made two mistakes:
I passed values to __construct to populate a GroupUser upon construction. I removed the __construct entirely, this didn't play nice with the API Platform and the automatic IRI creation.
I used a composite primary key on group_id and user_id for my GroupUser table, which I abandoned in favour of an own ID.
Pretty straight forward, here's what I added to the GroupUser entity:
/**
* #var int
*
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id
*/
private $id;
And don't forget the getter method:
public function getId(): ?int
{
return $this->id;
}

How to document choices in api platform?

In API-platform, how do I document the choices for a specific property? I have for example a property deliveryTimeWindow:
/**
* #var string The delivery timewindow of this order.
*
* #ORM\Column
* #Assert\Choice(callback="getTimeWindows")
* #Assert\NotBlank()
*/
private $deliveryTimeWindow;
But the only thing I see in the front-end is an example "string".
How do I inform to my end-users that only a specific choice is allowed?

Doctrine 2.0 One To Many Schema Validation Problem

class Account
{
...
/*
* #OneToMany(targetEntity="Address", mappedBy="account");
*/
private $addresses;
...
}
class Address
{
...
/**
* #ManyToOne(targetEntity="Account", inversedBy="addresses")
* #JoinColumn(name="account_id", referencedColumnName="id")
*/
private $account;
...
}
When i run console command to validate schema:
[Mapping] FAIL - The entity-class 'Entity\Address' mapping is invalid:
* The association Entity\Address#account refers to the inverse side
field Entity\Account#addresses which does not exist
Why?
I think the Doctrine annotation reader uses php's Reflection API, specifically getDocComment to read the annotations. That means your doc block comments must begin with /** otherwise they will be ignored. Your $addresses property annotation begins with /* so it won't get picked up.

Resources