Doctrine 2.0 One To Many Schema Validation Problem - validation

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.

Related

Using doctrine, apiplatform and openapi generator leads to duplcate id field in the frontend side, how to solve this?

I m using Symfony 4 with Doctrine and API Platform to build the backend of my application.
I generated entities using php bin/console make:entity. Each class has an id field like this one
/**
* #ApiResource()
* #ORM\Entity(repositoryClass=CategoryRepository::class)
*/
class Category
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
...
I generated the api doc file php bin/console api:openapi:export --output=swagger.json
Then I use OpenAPI Generator to generate the client part for the frontend npx openapi-generator-cli generate -i swagger.json -g typescript-angular --enable-post-process-file -o build
The resulting model file categoryJsonld.ts containes 2 id fields
export interface CategoryJsonld {
readonly context?: string | any | null;
readonly id?: string;
readonly type?: string;
readonly id?: number;
...
The field idis duplicated and the frontend doesn't compile.
I guess the first id stands for #id defined in jsons+ld format.
Any idea to solve/prevent the duplication of id ?
Thanks.
You can set your Content-Type to application/json to exclude the extra json-ld fields. However this will result in problems when you have collections and need to use pagination, because you would need some meta data that is not being returned with plain json. I would recommend changing your own id field to something else like uuid as the id, type and context names are reserved for json-ld format. Try just changing the getters and/or setters name first OpenApi should generate it correctly then. e.x. In your entity change getId() to getUuid() or you could just add an annotation to change the serialized name for example: #SerializedName("uuid") above the getId()

What is Doctrine default behavior for saving empty strings from POST? I would like to save NULL values

I am saving some values coming from a form (managed by CodeIgniter) into simple Doctrine entities. Some of my fields are optional.
Sample code of controller :
$entity->setDistance($this->input->post('distance'));
$entity->setKilometricRate($this->input->post('kilometric_rate'));
$this->getEntityManager()->persist($entity);
$this->getEntityManager()->flush($entity);
When I don't fill some fields in the form, then 0 is persisted into the database (since php converts "" in 0).
Sample code of entity :
/**
* #var float|null
*
* #ORM\Column(name="distance", type="float", precision=53, scale=0, nullable=true)
*/
private $distance;
/**
* #var float|null
*
* #ORM\Column(name="kilometric_rate", type="float", precision=53, scale=0, nullable=true)
*/
private $kilometricRate;
Is there a way to tell Doctrine to save NULL value instead of 0 or empty string, without testing all the strings in all the setters?
At the moment I am using a custom helper, but I am quite sure that there is a better way to do this.
Helper :
public function getValueOrNull(?string $postedVal):?string{
if ($postedVal==="0") return "0";
return (!empty($postedVal) ? $postedVal : null);
}
There are multiple places where you can tackle this. For example you could check how CodeIgniter handles forms and ensure that an empty fields returns null instead. I don't know CI well enough to say how easy it is and where this could cause side effects.
You could also deal with this in your entities making sure that whenever someone sets a value with an empty string it is converted to null:
class MyEntity
{
// ...
public function setDistance($distance)
{
if ($distance === '') {
$distance = null;
}
$this->distance = $distance;
}
}
If you want to always want to have this for floats you could create a custom type that extends float and modify the method convertToPHPValue. See https://www.doctrine-project.org/projects/doctrine-dbal/en/2.9/reference/types.html#custom-mapping-types

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 get Doctrine handling ENUMs correctly?

In an application I have a case of the Class Table Inheritance. The discriminator column is an ENUM:
/**
* Foo
*
* #ORM\Table(name="foos", ...)
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="`type`", type="string", columnDefinition="ENUM('bar', 'buz')")
* #ORM\DiscriminatorMap({
* "bar" = "Bar",
* "buz" = "Buz"
* })
*/
abstract class Foo
{
...
}
Doctrine works as expected (to begin with). The doctrine:migrations:diff command creates a migration for the tables and relationships and also defines the discriminator column correctly, as an ENUM.
Then I execute the migrations (doctrine:migrations:migrate). The schema looks well. But:
When I execute the diff command again (and expect no new migrations), I get a new migration generated:
final class Version20180619205625 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->addSql('ALTER TABLE foos CHANGE type `type` ENUM(\'bar\', \'buz\')');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE tasks CHANGE `type` type VARCHAR(255) DEFAULT NULL COLLATE utf8mb4_unicode_ci');
}
}
Alright, I execute it. And try the diff command again. And get the same migration generated again... So, Doctrine seems to "think", the column is still VARCHAR.
I showed the issue here on example of an inheritance discriminator. But actually it doesn't matter, if the column is a discriminator or not -- this behavior is the same for every ENUM column.
How to solve this issue? Is there a way make Doctrine handle ENUM columns correctly?

Validating values in entity - by setter or assert validation?

I have question about OOP strategy of validating values in entity. Lets say I have entity like this:
/*
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\PageBundle\Entity\PageRepository")
*/
class File {
/*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/*
* #ORM\Column(name="type", type="string")
*/
private $type;
/*
* #ORM\ManyToOne(targetEntity="File")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
}
Now I want to only entities type "parent" could be parents. I can do it in 2 ways.
Using Symfony Callback validation:
if ($this->getParent() != null && $this->getParent()->getType() != 'group')
$context->addViolationAt('parent', 'Invalid parent.', array(), null);
which is quite obvious, but works only when I call validator, or
put this logic in setter like it's proposed in Symfony book
You should check generated entities and adjust getter/setter logic to your own needs
for example:
setParent(File $parent) {
if ($parent->getType() != 'group')
throw new \Exception('Invalid parent');
$this->parent = $parent;
}
Which approach is better? Using Validation Constraints created specifically for this purpose or getters and setters?
If using Validation - should I always use default getters and setters or are they any fancy (and useful) things I can do inside them (any examples)?
I would use Symfony Callback validation, first off because it is a dedicated construct for validating entities and because your example is closer to the design philosophy of the component intended for callback validation.
While it is true that PHP OOP books encourage you to not only use getters and setters for retrieving property values, I've always used them to ensure default values and constraints on fields (i.e. I have an $id property, which is of type int, and I want to make sure to cast the value of the $id to int when setting it, even if somebody mistakenly sent in a string/whatever).
Beyond that, I also think it's weird to have a try/catch block for a setter, especially when I know that I'm sending in the proper argument type.
UPDATE: Just noticed the final part of your question. I use my Symfony getters and setters basically just for retrieving/storing values. I tend to set default values for things in constructors. I've seen some code over the years where the getters and setters do some pretty wild things, which really didn't belong within the scope of an Entity, but closer to a Service or Repository.
I think of Entities as the constructs allowing me to map the database table to a PHP class - they don't need to be smart, special, or do anything outside that scope.
I prefer keeping them simple and using the rest of the architecture to compliment the possible operations. Is it something that offers some general functionality and that can be isolated independently? Make a Service for that. Is it something to do with manipulating entities, retrieving certain info or handling them in a specific way? Make a Repository for that.

Resources