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.
Related
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.
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)...
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;
}
I've got 2 Bundles in my Symfony2 Framework:
A "MainBundle" with a User Entity, and a "TicketBundle" with all my Stuff for the Issue Tracking System. In this Case only the Entities "Pool" and "Mappingpooluser" are important.
They both use different entity mangers, because i have to use 2 different databases. (see my config.yml below)
doctrine:
dbal:
default_connection: default
connections:
default:
[driver, host etc.]
dbname: m_symfony
ticket:
[driver, host etc.]
dbname: m_ticketbundle
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
XXXMainBundle: ~
ticket:
connection: ticket
mappings:
XXXTicketBundle: ~
XXXMainBundle: ~
Now I have an Entity (Mappingpooluser) which needs one Entity of the MainBundle (User) and one Entity (Pool) of the TicketBundle (and some unimportant stuff):
/**
* XXX\TicketBundle\Entity\Mappingpooluser
*
* #ORM\Table(name="MappingPoolUser")
* #ORM\Entity(repositoryClass="XXX\TicketBundle\Repository\MappingPoolUserRepository")
*/
class Mappingpooluser
{
/**
* #var integer $poolid
*
* #ORM\OneToOne(targetEntity="Pool")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
private $pool;
/**
* #var integer $userid
*
* #ORM\OneToOne(targetEntity="XXX\MainBundle\Entity\User")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
private $user;
[getter/setter and this stuff]
Till this point everything works fine :)
I can fetch the Mappingpooluser Entity in the Controller via
$em = $this->getDoctrine()->getEntityManager("ticket");
$entities = $em->getRepository('XXXTicketBundle:Mappingpooluser')->findAll();
If I call $entities[0]->getPool()->getId() I will get the correct ID of the Pool (as mentioned: Pool is in the same Bundle like Mappingpooluser), but if I call $entities[0]->getUser()->getId() I will get an Error:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'm_ticketbundle.User' doesn't exist
Thats quit correct, because the User table is in the other DB (m_symfony).
Long Story, short Question: How do I get Symfony2 to use the ticket entity-manger for the Pool and the default entity-manger for the User??
The actual problem with your scenario is that you need to map the same entity (pool) to two different entity managers (default and ticket). What I would recommend in this scenario is creating a hierarchy in your entity layer for this entity:
base pool, contains all common fields
pool "default", contains specialized fields for default em
pool "ticket", contains specialized fields for ticket em
Then you can map them in different entity managers. If you use two different namespaces for default and ticket entities you can specify the folder from which mappings should be loaded in your em configuration like this:
mappings:
MyBundle:
type: annotation
dir: Path/To/Entities
The path is relative to the Bundles's root directory. So for example you could have Entity/Default and Entity/Ticket namespaces and map them independently while having common fields in an unmapped class in Entity namespace.
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.