API Platform - Entity Translation, Doctrine Translatable - doctrine-extensions

I'm trying to add KnpLabs Doctrine Behaviors - and precisely, the Translatable Behavior - on one of the entity in my API Platform project.
Here's what I've done so far :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
* #ApiResource
*/
class Article
{
use ORMBehaviors\Translatable\Translation,
ORMBehaviors\Timestampable\Timestampable
;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
}
And here's the Entity translation :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Traits as CustomTraits;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleTranslationRepository")
* #ApiResource
*/
class ArticleTranslation
{
use ORMBehaviors\Translatable\Translatable;
/**
* #var string
*
* #ORM\Column(name="someFieldToTranslate", type="string", length=255, nullable=true)
*/
private $someFieldToTranslate;
public function getSomeFieldToTranslate(){...}
public function setSomeFieldToTranslate($someFieldToTranslate){...}
}
Here's the basic "configuration" for getting Translatable Behavior working according to the doc.
Issues start when I try to update the DB schema : I got this error:
No identifier/primary key specified for Entity "App\Entity\ArticleTranslation". Every Entity must have an identifier/primary key in . (which is being imported from "/Sites/bookshop-api/config/routes/api_platform.yaml"). Make sure there is a
loader supporting the "api_platform" type.
However in Translatable Traits, there's already an ID and documentation precise that Translation Entity should only have fields we want to translate...
Anyway, I've put an ID to this ArtcleTranslation Entity to get rid of the error :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Traits as CustomTraits;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleTranslationRepository")
* #ApiResource
*/
class ArticleTranslation
{
use ORMBehaviors\Translatable\Translatable;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="content", type="string", length=255, nullable=true)
*/
private $content;
public function getContent(){...}
public function setContent($someContent){...}
}
From here, no error when I update the DB schema. Perfect !
Now I can take a look at the Swagger Documentation :
Everything looks fine ! But when I do take a look at the DB :
In Article table :
no "local" field
no "empty" field
In ArticleTranslation table :
no "translatable_id" field
no "currentLocal"
no "defaultLocal"
I guess it must be linked but in the swagger POST Tab, the model is different too.
article_translation model
I only tried /GET and /POST method on both entities, they're working (I can see it in DB) but no relation between the 2 of them.
I hope my post is not too long but I tried to be the more specific !
Thanks in advance

I did another answer as the first question was to resolve a mistake and not to explain how I integrated Knp Labs Doctrine Translations with Api Platform.
Summary :
We have an entity Article which has some fields translated inside an ArticleTranslation. We want to retrieve the entity through Api platform with its translations and we want to add or update translations through the api.
What I did :
1 - Article entity has a use ORMBehaviors\Translatable\Translatable. If we look inside of this trait, it has 2 attributes : $translations and $newTranslations. We need to expose these attributes inside the Article entity :
class Article {
use ORMBehaviors\Translatable\Translatable;
protected $translations;
protected $newTranslations;
}
Now we have a new attribute translations which is automatically filled from ArticleTranslation when we are getting some Article from the api
2 - Now we want to add/edit some translations : We need to fill the newTranslations attribute inside the Article when we are sending to the api:
"newTranslations": {
"en": {
"description": "Firstname"
},
"fr": {
"description": "Prénom"
}
}
Now we are receiving the new translations into the api but it's not persisted because we have to call the function mergeNewTranslations(). This function just take all translations inside the attribute $newTranslations and merge it with the $translations attribute in order to persist it.
3 - I created a new trait that I called TranslatableOverride. I imported it on directly on my Entity next to ORMBehaviors\Translatable\Translation:
trait TranslatableOverride
{
/**
* Set collection of new translations.
*
* #return ArrayCollection
*/
public function setNewTranslations($newTranslations)
{
if ($newTranslations) {
foreach ($newTranslations as $locale => $translations) {
foreach ($translations as $key => $value) {
$tr = $this->translate($locale);
$setter = 'set' . ucfirst($key);
if (method_exists($tr, $setter)) {
$tr->{$setter}($value);
}
}
}
$this->mergeNewTranslations();
}
}
}
I'm not sure if it's pretty but it works like a charm with api-platform.
I didn't think about getting only one translation at a time. For the moment, I retrieve my entities with the whole bunch of translations which is definitely not efficient. I will add an override for the getTranslations in my override trait I guess.

I think you did a mistake. You have an entity Article which should be the translatable and you want to translate some fields which will be the translations?
So you should make the opposite, put the use ORMBehaviors\Translatable\Translatableon the Article and the use ORMBehaviors\Translatable\Translation on the ArticleTranslation
The fix for your Article entity :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
* #ApiResource
*/
class Article
{
use ORMBehaviors\Translatable\Translatable,
ORMBehaviors\Timestampable\Timestampable
;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
}
and the fix for your ArticleTranslation entity :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Traits as CustomTraits;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleTranslationRepository")
* #ApiResource
*/
class ArticleTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #var string
*
* #ORM\Column(name="someFieldToTranslate", type="string", length=255, nullable=true)
*/
private $someFieldToTranslate;
public function getSomeFieldToTranslate(){...}
public function setSomeFieldToTranslate($someFieldToTranslate){...}
}
Let me know if everything is alright now.

Related

Symfony 4 + JMS Serializer - Internal Server Error

I have a problem with serialization data.
I have two entities, which are connection relation.
These are my entities:
Task.php
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
*/
class Task
{
/**
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*
*/
private $id;
/**
* #JMS\MaxDepth(1)
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="task")
*
*/
private $user;
User.php
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="app_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Task", mappedBy="user")
*/
private $task;
public function __construct()
{
I would like to create API, and I serialization my data with JMS. I get this error:
Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
This is my controller:
public function getAllAction(): JsonResponse
{
$tasks = $this->taskService->getAll();
$serializer = SerializerBuilder::create()->build();
$data = $serializer->serialize($tasks, 'json',
SerializationContext::create()->enableMaxDepthChecks());
return new JsonResponse($data, 200, [], true);
}
Function getAll in my controller return data with table Task.
Please, help me with my problem. :)
Thanks, Friends.
I did not use JMS Serializer but i can suggest different solution like symfony serializer component.
public function getAllAction(SerializerInterface $serializer): JsonResponse
{
$data = $serializer->serialize($this->taskService->getAll(), 'json');
return new JsonResponse($data, 200, [], true);
}
Optional you can set serialization group to avoid circular reference error.
For each entity atribute that you want to serialize, set:
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Column(name="title", type="string", nullable=false, length=150)
* #Assert\Type("string")
* #Assert\NotBlank()
* #Groups({"group_name"})
*/
private $title;
/**
* #ORM\Column(name="description", type="string", nullable=true, length=255)
* #Assert\Type("string")
*/
private $description;
And then you serialize only this field with specific group:
$serializer->serialize($this->taskService->getAll(), 'json', ['groups' => ['group_name']])
Doc: https://symfony.com/doc/4.1/serializer.html#using-serialization-groups-annotations
PS. Action suffix isnt required now
https://symfony.com/doc/current/best_practices/controllers.html#controller-action-naming

ApiPlatform: How to update instead of create a child entity that is not a #ApiResource nor a #ApiSubresource

I have a ThirdPartyEntity from a third party bundle that, using a ThirdPartyEntityTrait, I link to MyEntity in my project.
Now, as the ThirdPartyEntity is not set a ApiResource nor as an ApiSubresource and as I don't have any serializaton group set on MyEntity, when I get MyEntity from ApiPlatform, it returns me something like this:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":22,
"a_property":"some value"
}
}
BUT IF I PUT a changed value for a_property with this body:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":22,
"a_property":"some NEW value to update"
}
}
I get a new third_party_entity to be created and get this response:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":23,
"a_property":"some NEW value to update"
}
}
SO, HOW CAN I UPDATE third_party_entity instead of creating it each time?
HERE THERE ARE THE INVOLVED CLASSES AND TRAITS
/**
* #ORM\Table(name="app_my_entities")
* #ORM\Entity()
* #ApiResource()
*/
class MyEntity
{
// !!!!!!!!!!!!!!!!!!
// This is the trait I use to link MyEntity
// with the entity from the third-party bundle
// !!!!!!!!!!!!!!!!!!
use ThirdPartyEntityTrait;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
...
}
And this is the ThirdPartyEntityTrait:
trait ThirdPartyEntityTrait
{
/**
* #ORM\OneToOne(targetEntity="Namespace\To\Bundle\Entity\ThirdPartyEntity", cascade={"all"})
* #ORM\JoinColumn(name="thirdPartyEntity", referencedColumnName="id")
*/
private $thirdPartyEntity;
/**
* #param thirdPartyEntity $thirdPartyEntity
*
* #return ThirdPartyEntity
*/
public function setThirdPartyEntity(thirdPartyEntity $thirdPartyEntity): ThirdPartyEntity
{
$this->thirdPartyEntity = $thirdPartyEntity;
/** #var ThirdPartyEntity $this */
return $this;
}
/**
* #return thirdPartyEntity
*/
public function getThirdPartyEntity(): ?thirdPartyEntity
{
return $this->thirdPartyEntity;
}
/**
* #return thirdPartyEntity
*/
public function removeThirdPartyEntity(): ?thirdPartyEntity
{
$thirdPartyEntity = $this->getThirdPartyEntity();
$this->thirdPartyEntity = null;
return $thirdPartyEntity;
}
}
As you can see, nothing more a property to save the relation and some accessors methods.
This is, instead, the linked Entity:
/**
* #ORM\Entity()
* #ORM\Table(name="third_party_entities")
*/
class ThirdPartyEntity
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="aProperty", type="string", nullable=true)
*/
private $aProperty;
public function getId()
{
return $this->id;
}
public function getAProperty()
{
return $this->aProperty;
}
public function setAProperty($aProperty)
{
$this->aProperty = $aProperty;
return $this;
}
}
This question is cross-posted also on GitHub.
The solution was pretty simple: use another config method!
Practically, it is possible to mix configuration types and so, it is possible to use the annotations along with the yaml configuration.
Given this, it is sufficient to create a new config file in config/api_platform/third_party_entity.yaml.
In it put the configuration required to map the entity from the third party bundle:
resources:
App\Entity\MyEntity:
properties:
remote:
subresource:
resourceClass: 'Third\Party\Bundle\TheBundle\Entity\ThirdPartyEntity'
Third\Party\Bundle\TheBundle\Entity\ThirdPartyEntity:
This way it is possible to configure as subresource the entity from the third party bundle to which we don't have access with annotations.

symfony create query builder

I'm newbie in symfony and I would like some advices for creating a query builder statement. Is pretty simple, the idea is to get the data from a third entity.
I have the entities:
/**
* Entity
* #ORM\Entity(repositoryClass="LoungepassBundle\Entity\LoungepassRepository")
* #ORM\Table(name="loungepass_loungepass")
*
*/
class Loungepass{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Agency", inversedBy="loungepasses")
* #ORM\JoinColumn(name="agency_id", referencedColumnName="iata8", nullable=false)
*/
private $agency;
//...
}
Agency Entity
class Agency {
/**
* #ORM\Id
* #ORM\Column(type="string", length=8, name="iata8")
*/
protected $id;
//...
/**
* #ORM\OneToMany(targetEntity="LoungepassBundle\Entity\Loungepass", mappedBy="agency")
*/
protected $loungepasses;
/**
* #var Market
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Market")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="sales_country", referencedColumnName="id")
* })
*/
private $market;
//...
}
I would like to do a query, like this:
public function queryBySlugInContext($slug, $user) {
$query = $this->createQueryBuilder("l")
->where('l.slug = :slug')
->setParameter('slug', $slug);
if(count($user->getAgencies()) > 0){
$query->andWhere(':agencyIds MEMBER OF l.agencies')
->setParameter('agencyIds',$user->getAgencies());
}
Able to access the information from the market attribute located in the agency entity.So basically, retrieve information from the Market Entity.
any suggestions?
Thank you in advance
I modified a bit your code but the mind is here
When you have to access to retrieve data from other tables just keep in mind to use differents mysql join accessible with doctrine
public function queryBySlugInContext($slug, $user) {
$query = $this->createQueryBuilder("l")
->where('l.slug = :slug')
->setParameter('slug', $slug);
if(0 !== count($user->getAgencies())) {
$query = $query->innerJoin('l.agency', 'a')
->andWhere('a.id IN(:userAgenciesIds)')
->setParameter('userAgenciesIds', array_map(function(Agency $agency) {
return $agency->getId();
}, $user->getAgencies());
}
You should take a time to read some content on internet like for example the doctrine query builder documentation

TYPO3 - extending an extbase extension with new fields and using these in fluid templates

I'm trying to extend powermail (version 2) with the possibility to add a note for each input field. So far I have created a new extension using extension builder and with a few modifications to ext_tables.php the field show up in the backend. The new field is called 'note' and I thought I could just do something like {field.note} in the fluid template input.html, but that does not work. My model includes the setter and getter:
class Tx_Formnotes_Domain_Model_Powermailnotes extends Tx_Extbase_DomainObject_AbstractEntity {
/**
* note
*
* #var string
*/
protected $note;
/**
* Returns the note
*
* #return string $note
*/
public function getNote() {
return $this->note;
}
/**
* Sets the note
*
* #param string $note
* #return void
*/
public function setNote($note) {
$this->note = $note;
}
}
What else is needed?
Info: I'm using TYPO3 4.7
You could map the powermail model like
config.tx_extbase.persistence.classes {
Tx_Formnotes_Domain_Model_Powermailnotes {
mapping {
tableName = powermailTableName
columns {
exampleMedia.mapOnProperty = media
}
}
}
}
after that you should extend your TCA with these properties. At least you can write setter and getter for each property and use them in your fluid template.

Doctrine 2 : OneToMany relation, Entity is not loaded

Here is my problem : I have the 3 entities Item, User and Link above (these classes also have the usual getters and setters).
class User {
//...
/*
* #ORM\OneToMany(targetEntity="Link", mappedBy="user", cascade={"persist", "remove"})
*
*/
protected $links;
//...
}
class Item {
//...
/*
* #ORM\OneToMany(targetEntity="Link", mappedBy="item", cascade={"persist", "remove"})
*
*/
protected $links;
//...
}
class Link {
/**
* #var datetime $time
*
* #ORM\Column(name="time", type="datetime")
*/
private $time;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Item", inversedBy="links")
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
*/
private $item;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="links")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
//...
}
I did not use a ManyToMany relationship because of the $time property in the Link class.
When I create a Link, I do it this way :
$link = getExistingLink($item, $user);
if (!$link) {
$link = new Link();
$link->setItem($item);
$link->setUser($user);
}
$link->setTime(new \DateTime());
$em = $this->getEntityManager();
$em->persist($link);
$em->flush();
The data is written in the database, however when I call $user->getLinks(), it returns NULL. I event tried to do this :
$user->addLink($link);
$em->persist($user);
$em->flush();
But the link won't be loaded the next time the $user will be loaded.
Any idea why the Link entities are not loaded ?
OK Problem Solved.
The annotations starts with /* instead of /** in both User and Item classes.
Just a silly mistake ...