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

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)...

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, Error when doing POST. Assert trigger despite that the data is sent

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.

Inherited Entity with self-referencing ManyToMany: EXTRA_LAZY fetch mode not working

I have to following setup: A parent class
/**
* #ORM\Entity()
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
*/
abstract class DataCategory
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//...
}
and several derived classes which hold references to the parent (only showing here one)
/**
* #ORM\Entity
*/
class MultiCompoundDataCategory extends DataCategory
{
/**
* #ORM\ManyToMany(targetEntity="DataCategory", fetch="EXTRA_LAZY")
* #ORM\JoinTable(name="multi_compound_data_category_data_category",
* joinColumns={#ORM\JoinColumn(name="multi_compound_data_category", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="data_category", referencedColumnName="id")})
*/
public $summands;
public function __construct()
{
$this->summands = new ArrayCollection();
}
}
Now when loading all MultiCompoundDataCategories via
$this->getDoctrine()->getRepository(MultiCompoundDataCategory::class)->findAll(), not only one select on MultiCompoundDataCategories is issued (with a join on the ManyToMany-table). Instead I get one main query, followed by one query for each summand, each with a big fat sequence LEFT JOIN (also the docs contain a warning about this problem, I am massively referencing non-leaf nodes of the inheritance tree).
BTW: Wouldn't LAZY fetching already suffice to avoid loading the ManyToMany ?
From this comment, I assume the problem is that my child entities refencence the parent one. How can I circumvent this? MappedSuperclass?
Ok, I found something but don't know if there exists a better solution.
This blog post gave me inspriation to a solution (last paragraph).
By performing a DQL fetch join, I can eagerly load a child entity with hydrated references. So what I do is to load all child classes.
$all=[];
$all['sampled'] = $em->createQuery("SELECT s FROM ".SampledDataCategory::class." s")->getResult();
$all['compound'] = $em->createQuery("SELECT c, s, s2 FROM ".CompoundDataCategory::class." c JOIN c.cat1 s JOIN c.cat2 s2")->getResult();
$all['unary'] = $em->createQuery("SELECT u, s FROM ".UnaryDataCategory::class." u JOIN u.dataCategory s")->getResult();
$all['multi'] = $em->createQuery("SELECT m, s FROM ".MultiCompoundDataCategory::class." m JOIN m.summands s")->getResult();
After that, the many SELECT queries for every single referenced DataCategory are not necessary, because the entity manager has seen it already.
This sounds weird, but up to now my tests indicate that 4 queries with lots of hydration workload run much faster than ~800 single selects.

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;
}

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