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

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

Related

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

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.

Symfony2, Doctrine2: non english/letin slug

i have a community, where users can make their own topics, also in international languages like hebrew or arabic
i have the "name" field of the topic, and i want to generate slugs out of it
now i have tested it with arabic, using the stofdoctrineBundle and Doctrine extensions
i set up the slug like this
/**
* #Gedmo\Slug(fields={"name"})
* #ORM\Column(length=255, unique=true, nullable=false)
*/
private $slug;
and when i try to make a new topic with arabic, say this string "علي قرا", doctrine sluggable generated the following slug: ly-qr
so my goal is to have complete URL like this
http://www.mysite.com/علي-قرا
for SEO reasons.
Florian, i tried your way, but it looked way too complicated for my needs, so i went an easier path:
completely disabled doctrine extensions
created a new class under Utils\MyFunctions, and put the static method slugify which is based on symfony2 blog tutorial.
when setting the name, i also set the slug with it
public function setName($name)
{
$this->name = $name;
$this->setSlug($this->name);
}
public function setSlug($slug)
{
//now we generate the slug, from the utilities my function
$slug = Myfunctions::slugify($slug);
$this->slug = $slug;
}
Problem solved, and arabic now showing fine as a slug.
The default behavior of the slugifier in gedmo's doctrine extensions is to convert all characters to ASCII.
If you don't want this behavior, you will have to create either your own handler and/or your own transliterator method.
The docs here explain well how to do it.

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