Git Product home page Git Product logo

apiplatformtranslationbundle's Introduction

Locastic Api Translation Bundle

Translation bundle for ApiPlatform based on Sylius translation

Installation:

$ composer require locastic/api-platform-translation-bundle

Implementation:

Translatable entity:

  • Extend your model/resource with Locastic\ApiTranslationBundle\Model\AbstractTranslatable
  • Add createTranslation() method which returns new object of translation Entity. Example:
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;

class Post extends AbstractTranslatable
{
    // ...
    
    protected function createTranslation(): TranslationInterface
    {
        return new PostTranslation();
    }
}
  • Add a translations-property. Add the translations serializations group and make a connection to the translation entity:
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;

class Post extends AbstractTranslatable
{
    // ...
    
    /**
     * @ORM\OneToMany(targetEntity="PostTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     *
     * @Groups({"post_write", "translations"})
     */
    protected $translations;
}
  • Add virtual fields for all translatable fields, and add read serialization group. Getters and setters must call getters and setters from translation class. Example:
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Symfony\Component\Serializer\Annotation\Groups;

class Post extends AbstractTranslatable
{
    // ...
    
    /**
    * @Groups({"post_read"})
    */
    private $title;
    
    public function setTitle(string $title)
    {
        $this->getTranslation()->setTitle($title);
    }

    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }
}

Translation entity:

  • Add entity with all translatable fields. Name needs to be name of translatable entity + Translation
  • Extend Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation
  • Add serialization group translations to all fields and other read/write groups. Example Translation entity:
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;

class PostTranslation extends AbstractTranslation
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="Post", inversedBy="translations")
     */
    protected $translatable;
    
    /**
     * @ORM\Column(type="string")
     * 
     * @Groups({"post_read", "post_write", "translations"})
     */
    private $title;
    
    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"post_write", "translations"})
     */
    protected $locale;

    public function setTitle(string $title): void
    {
        $this->title = $title;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }
}

Api resource

  • Add translation.groups filter if you would like to have option to return all translation objects in response. If you don't use translations group, response will return only requested locale translation or fallback locale translation.
  • Add translations to normalization_context for PUT and POST methods to make sure they return all translation objects.
  • Example:
AppBundle\Entity\Post:
    itemOperations:
        get:
            method: GET
        put:
            method: PUT
            normalization_context:
                groups: ['translations']
    collectionOperations:
        get:
            method: GET
        post:
            method: POST
            normalization_context:
                groups: ['translations']
    attributes:
        filters: ['translation.groups']
        normalization_context:
            groups: ['post_read']
        denormalization_context:
            groups: ['post_write']

Usage:

Language param for displaying single translation:

?locale=de

Or use Accept-Language http header

Accept-Language: de

Serialization group for displaying all translations:

?groups[]=translations

POST translations example

{
    "datetime":"2017-10-10",
    "translations": { 
        "en":{
            "title":"test",
            "content":"test",
            "locale":"en"
        },
        "de":{
            "title":"test de",
            "content":"test de",
            "locale":"de"
        }
    }
}

EDIT translations example

{
    "datetime": "2017-10-10T00:00:00+02:00",
    "translations": {
        "de": {
          "id": 3,
          "title": "test edit de",
          "content": "test edit de",
          "locale": "de"
        },
        "en": {
          "id": 2,
          "title": "test edit",
          "content": "test edit",
          "locale": "en"
        }
    }
}

Contribution

If you have idea on how to improve this bundle, feel free to contribute. If you have problems or you found some bugs, please open an issue.

Support

Want us to help you with this bundle or any Api Platform/Symfony project? Write us an email on [email protected]

apiplatformtranslationbundle's People

Contributors

alanpoulain avatar alex--c avatar antonioperic avatar arnoudthibaut avatar coalajoe avatar gonzaloalonsod avatar guillaume-sainthillier avatar jewome62 avatar k-37 avatar konradkozaczenko avatar luca-nardelli avatar margauxfeslard avatar oipnet avatar pascal-zarrad avatar paullla avatar pgrimaud avatar samnela avatar spartakusmd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apiplatformtranslationbundle's Issues

Too many db queries with collectionOperations

First of all thank you for this good bundle.

It works very well.

But looking more closely at the number of doctrine queries via the symfony profiler.
I notice that there are a lot of duplicate requests when we get the translations.

Example: if the collection contains 6 elements, we have 6 times the same request.

SELECT t0.id AS id_1, t0.title AS title_2, t0.content AS content_3, t0.meta_description AS meta_description_4, t0.seo_title AS seo_title_5, t0.slug AS slug_6, t0.locale AS locale_7, t0.translatable_id AS translatable_id_8 FROM blog_post_translation t0 WHERE (t0.locale = ? AND t0.translatable_id = ?)
Parameters:
[▼
  "en"
  1
]

I had the same problem with Gedmo\Translatable and I used the TranslationWalker. Would it be possible to use something equivalent?

use Doctrine\ORM\Query;
use Gedmo\Translatable\Query\TreeWalker\TranslationWalker;
...
$query->setHydrationMode(TranslationWalker::HYDRATE_OBJECT_TRANSLATION);
$query->setHint(Query::HINT_REFRESH, true);

Thanks

Symfony 4 refuse to load ApiPlatformTranslationBundle

I try to add the bundle in bundles.php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    
    Locastic\ApiPlatformTranslationBundle\ApiPlatformTranslationBundle::class => ['all' => true]

];

And i have this error

PHP Fatal error: Uncaught Symfony\Component\Debug\Exception\ClassNotFoundException: Attempted to load class "ApiPlatformTranslationBundle" from namespace "Locastic\ApiPlatformTranslationBundle".

It's normal ? I try this because the services of ApiPlatformTranslationBundle not load

Api pack dependency - 1.2 not working

Tried to install the package via composer, got an error:

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for locastic/api-platform-translation-bundle dev-master -> satisfiable by locastic/api-platform-translation-bundle[dev-master].
    - locastic/api-platform-translation-bundle dev-master requires api-platform/api-pack ^1.1 -> satisfiable by api-platform/api-pack[1.1.0, v1.2.0] but these conflict with you
r requirements or minimum-stability.


Installation failed, reverting ./composer.json to its original content.

I guess it's only matter of incorrectly defined dependency on api-pack instead of api-core. Refering to #4. Can you please change composer.json to make it work?

I have to use Gedmo which philosophy and approach I cannot agree instead of Locastic. Thanks.

unique constraint " name + locale "

hello !

i try to add unique constraint on my translations with name + locale fields to not have duplications on my translation entities but when i try to remplace my translations collection of my translatable entity without any modifications, my constraint is trigged because doctrine try to add new translations ( instead of update existing ).

I analyse the unique constraint validator of symfony and i see i loss my id on translations objects when valid is called. someone have any idea ?

Rédoine

No locale has been set and current locale is undefined.

Hello,

I’m trying to use LocasticApiTranslationBundle with doctrine but I’m unable to make it work.

it tells me that there is no locale defined but I don't know where to declare the locale ?

Can you help me and tell me what I’m doing wrong please ?

In attachment, the stacktrace,

the source entity (ReferenceMateriel) and the translation entity (ReferenceMaterielTranslation) I use.

Thank you in advance for your help.

Best regards

ReferenceMateriel.txt
ReferenceMaterielTranslation.txt
stacktrace.txt

Problems with setup

Hey there.
I am trying to setup translations with nested relationships, when posting a new entity I get the following error: Could not denormalize object of type \"Locastic\\ApiPlatformTranslationBundle\\Model\\TranslationInterface[]\", no supporting normalizer found

Do you have a solution for this?

Filter on current locale translations

Hello !

I am trying to filter on current locale translations but i don't find any way to do that..

This is working for filter on all translations:
#[ApiFilter(SearchFilter::class, properties: ['translations.name'])]

I also want to order my list by current translation name:

'domains_list' => [ // @todo order
            'method' => 'GET',
            'path' => '/domains',
            'access_control' => 'is_granted(\''.Account::ROLE_USER.'\')',
            'normalization_context' => ['groups' => ['domains:list', 'domains:translations']],
            'order' => ['translations.name' => 'ASC'], // <- here i want to order on current locale
        ],

Someone have any idea to do that without rewriting all filter class ?

Thanks :)

Error after update to Doctrine 3

Hello,

I've just updated my installation with doctrine3 and now i've got this error

Argument 1 passed to Locastic\ApiPlatformTranslationBundle\EventListener\AssignLocaleListener::postLoad() must be an instance of Doctrine\Common\Persistence\Event\LifecycleEventArgs, instance of Doctrine\ORM\Event\LifecycleEventArgs given, called in /var/www/html/api/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php on line 58",

Any idea please ?

Loader Load Exception

Hi,

I want to user this bundle for first time.
I folow Implementation steps but i get load exception.

Class "App\Entity\PostTranslation" sub class of "Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation" is not a valid entity or mapped super class in . (which is being imported from "/Users/xxx/Code/www/symfony/apiTranslationTest/config/routes/api_platform.yaml"). Make sure there is a loader supporting the "api_platform" type.

Did I skip something in the implementation?

-- Post.php

<?php

namespace App\Entity;

use App\Repository\PostRepository;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ORM\Entity(repositoryClass=PostRepository::class)
 */
class Post extends AbstractTranslatable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"post_read", "post_write"})
     */
    private $title;

    /**
     * @ORM\Column(type="string", length=255)
     * @Gedmo\Slug(fields={"title"})
     */
    private $slug;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Groups({"post_read", "post_write"})
     */
    private $subtitle;

    /**
     * @ORM\Column(type="text", nullable=true)
     * @Groups({"post_read", "post_write"})
     */
    private $content;

    /**
     * @ORM\OneToMany(targetEntity="PostTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     *
     * @Groups({"post_write", "translations"})
     */
    protected $translations;

    protected function createTranslation(): TranslationInterface
    {
        return new PostTranslation();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }

    public function setTitle(string $title): self
    {
        $this->getTranslation()->setTitle($title);

        return $this;
    }

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }

    public function getSubtitle(): ?string
    {
        return $this->subtitle;
    }

    public function setSubtitle(?string $subtitle): self
    {
        $this->subtitle = $subtitle;

        return $this;
    }

    public function getContent(): ?string
    {
        return $this->content;
    }

    public function setContent(?string $content): self
    {
        $this->content = $content;

        return $this;
    }
}

-- PostTranslation.php

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;
use Symfony\Component\Serializer\Annotation\Groups;

class PostTranslation extends AbstractTranslation
{

    /**
     * @ORM\ManyToOne(targetEntity="Post", inversedBy="translations")
     */
    protected $translatable;

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"post_read", "post_write", "translations"})
     */
    private $title;

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"post_write", "translations"})
     */
    protected $locale;

    public function setTitle(string $title): void
    {
        $this->title = $title;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }
}

validation of the fields of my entity with PUT REQUEST

Hello ,
my problem it's with the validation of the fields of my entity.
Example:
in my class

  /**
  * @ORM \ Entity ()
  * @UniqueEntity (fields = {"locale", "title"})
  **/
  class CategoryTranslation extends AbstractTranslation

i used the UniqueEntity annotation to prevent using the same word more two times with same local value, with my POST requests there are not problem but when I try to use PUT requests
the bundle does not UPDATE the data, it removes the records to insert new values so I get this error :
"detail": "translations [fr] .locale: This value is already used."

when I send this json to my endpoint:

 {
   "translations": [
     {
       "title": "natures",
       "locale": "fr"
     },
      {
       "title": "nature",
       "locale": "en"
     }
   ]
}

someone can give any hints ?
thank you in advance

Translations create not working as expected when GraphQl is used

Thanks for your great work in advance!

The translations are working very well.

But when GraphQl is used there is an issue related to the Translation entity serialization proccess.

Entities related

  • RecipeSteps as main entity
  • RecipeStepsTranslation as translation entity also declared as @ApiResource

Workflow

  1. Use createRecipeStepsTranslation mutation returning the IRI [success]
  2. Create an array of string to pass to next mutation [success]
  3. Use the createRecipeSteps mutation [fail]

The request made is described as follow:

// the parameters are the same suggested by GraphQl docs
mutation createRecipeSteps($cookOrder: Int!, $translations: [String]) {
  createRecipeSteps(input: {cookOrder: $cookOrder, translations: $translations}) {
    recipeSteps {
      id
    }
  }
}

// set of variables
{
  "translations": [
    "/api/recipe_steps_translations/1863",
    "/api/recipe_steps_translations/1864",
    "/api/recipe_steps_translations/1865"
  ],
  "cookOrder": 0
}

After send the query the next error is raised

{
  "errors": [
    {
      "debugMessage": "The type of the key \"0\" must be \"string\", \"integer\" given.",
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createRecipeSteps"
      ],
      "trace": [
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\Serializer\\AbstractItemNormalizer.php",
          "line": 737,
          "call": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer::denormalizeCollection('translations', instance of ApiPlatform\\Core\\Metadata\\Property\\PropertyMetadata, instance of Symfony\\Component\\PropertyInfo\\Type, 'App\\Entity\\RecipeStepsTranslation', array(3), 'graphql', array(8))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\Serializer\\AbstractItemNormalizer.php",
          "line": 403,
          "call": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer::createAttributeValue('translations', array(3), 'graphql', array(8))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\GraphQl\\Serializer\\ItemNormalizer.php",
          "line": 128,
          "call": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer::setAttributeValue(instance of App\\Entity\\RecipeSteps, 'translations', array(3), 'graphql', array(8))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\symfony\\serializer\\Normalizer\\AbstractObjectNormalizer.php",
          "line": 336,
          "call": "ApiPlatform\\Core\\GraphQl\\Serializer\\ItemNormalizer::setAttributeValue(instance of App\\Entity\\RecipeSteps, 'translations', array(3), 'graphql', array(8))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\Serializer\\AbstractItemNormalizer.php",
          "line": 250,
          "call": "Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer::denormalize(array(2), 'App\\Entity\\RecipeSteps', 'graphql', array(8))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\Serializer\\ItemNormalizer.php",
          "line": 70,
          "call": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer::denormalize(array(2), 'App\\Entity\\RecipeSteps', 'graphql', array(7))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\symfony\\serializer\\Serializer.php",
          "line": 208,
          "call": "ApiPlatform\\Core\\Serializer\\ItemNormalizer::denormalize(array(2), 'App\\Entity\\RecipeSteps', 'graphql', array(6))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\GraphQl\\Resolver\\Stage\\DeserializeStage.php",
          "line": 57,
          "call": "Symfony\\Component\\Serializer\\Serializer::denormalize(array(2), 'App\\Entity\\RecipeSteps', 'graphql', array(6))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\GraphQl\\Resolver\\Factory\\ItemMutationResolverFactory.php",
          "line": 98,
          "call": "ApiPlatform\\Core\\GraphQl\\Resolver\\Stage\\DeserializeStage::__invoke(null, 'App\\Entity\\RecipeSteps', 'create', array(6))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 624,
          "call": "ApiPlatform\\Core\\GraphQl\\Resolver\\Factory\\ItemMutationResolverFactory::ApiPlatform\\Core\\GraphQl\\Resolver\\Factory\\{closure}(null, array(1), null, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 550,
          "call": "GraphQL\\Executor\\ReferenceExecutor::resolveFieldValueOrError(instance of GraphQL\\Type\\Definition\\FieldDefinition, instance of GraphQL\\Language\\AST\\FieldNode, instance of Closure, null, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 474,
          "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: Mutation, null, instance of ArrayObject(1), array(1))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 858,
          "call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'createRecipeSteps')"
        },
        {
          "call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'createRecipeSteps')"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 860,
          "function": "array_reduce(array(1), instance of Closure, array(0))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 490,
          "call": "GraphQL\\Executor\\ReferenceExecutor::promiseReduce(array(1), instance of Closure, array(0))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 263,
          "call": "GraphQL\\Executor\\ReferenceExecutor::executeFieldsSerially(GraphQLType: Mutation, null, array(0), instance of ArrayObject(1))"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 215,
          "call": "GraphQL\\Executor\\ReferenceExecutor::executeOperation(instance of GraphQL\\Language\\AST\\OperationDefinitionNode, null)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\Executor\\Executor.php",
          "line": 156,
          "call": "GraphQL\\Executor\\ReferenceExecutor::doExecute()"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\GraphQL.php",
          "line": 162,
          "call": "GraphQL\\Executor\\Executor::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, null, array(2), 'createRecipeSteps', null)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\webonyx\\graphql-php\\src\\GraphQL.php",
          "line": 94,
          "call": "GraphQL\\GraphQL::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, 'mutation createRecipeSteps($cookOrder: Int!, $translations: [String]) {\n  createRecipeSteps(input: {cookOrder: $cookOrder, translations: $translations}) {\n    recipeSteps {\n      id\n    }\n  }\n}\n', null, null, array(2), 'createRecipeSteps', null, null)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\GraphQl\\Executor.php",
          "line": 34,
          "call": "GraphQL\\GraphQL::executeQuery(instance of GraphQL\\Type\\Schema, 'mutation createRecipeSteps($cookOrder: Int!, $translations: [String]) {\n  createRecipeSteps(input: {cookOrder: $cookOrder, translations: $translations}) {\n    recipeSteps {\n      id\n    }\n  }\n}\n', null, null, array(2), 'createRecipeSteps', null, null)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\api-platform\\core\\src\\GraphQl\\Action\\EntrypointAction.php",
          "line": 86,
          "call": "ApiPlatform\\Core\\GraphQl\\Executor::executeQuery(instance of GraphQL\\Type\\Schema, 'mutation createRecipeSteps($cookOrder: Int!, $translations: [String]) {\n  createRecipeSteps(input: {cookOrder: $cookOrder, translations: $translations}) {\n    recipeSteps {\n      id\n    }\n  }\n}\n', null, null, array(2), 'createRecipeSteps')"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\symfony\\http-kernel\\HttpKernel.php",
          "line": 157,
          "call": "ApiPlatform\\Core\\GraphQl\\Action\\EntrypointAction::__invoke(instance of Symfony\\Component\\HttpFoundation\\Request)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\symfony\\http-kernel\\HttpKernel.php",
          "line": 79,
          "call": "Symfony\\Component\\HttpKernel\\HttpKernel::handleRaw(instance of Symfony\\Component\\HttpFoundation\\Request, 1)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\vendor\\symfony\\http-kernel\\Kernel.php",
          "line": 195,
          "call": "Symfony\\Component\\HttpKernel\\HttpKernel::handle(instance of Symfony\\Component\\HttpFoundation\\Request, 1, true)"
        },
        {
          "file": "W:\\projects\\apps\\sample-api\\public\\index.php",
          "line": 20,
          "call": "Symfony\\Component\\HttpKernel\\Kernel::handle(instance of Symfony\\Component\\HttpFoundation\\Request)"
        }
      ]
    }
  ],
  "data": {
    "createRecipeSteps": null
  }
}

When I use another parameters as array of IRIs of previously saved objects this is not happening, only fails with translation.

Would it be posible the translations are not available to save this way when using GraphQl?

Usage question about Api Platforms json-response

I'm playing with this bundle on Api Platform 3. I've setup some examples and my responses for a GET-Request on http://localhost/test_entities/91656d77-1db8-4294-a841-004dc365fbb6 look like

{
  "@context": "/contexts/TestEntity",
  "@type": "TestEntity",
  "id": "91656d77-1db8-4294-a841-004dc365fbb6",
  "name": "deutscher name",
  "description": "deutsche beschreibung",
  "products": [],
  "translations": {
    "de": {
      "@type": "TestEntityTranslation",
      "@id": "/.well-known/genid/6b50a3758975475ca57e",
      "id": "192620a2-c8a1-45bd-8809-785e1a27f4cb",
      "name": "deutscher name",
      "description": "deutsche beschreibung",
      "locale": "de"
    },
    "en": {
      "@type": "TestEntityTranslation",
      "@id": "/.well-known/genid/dc4fadb73b87e7475f3f",
      "id": "e0d4ef95-f21c-4318-b182-9b00ed627de8",
      "name": "english name",
      "description": "english description",
      "locale": "en"
    }
  },
  "translation": {
    "@type": "TestEntityTranslation",
    "@id": "/.well-known/genid/6b50a3758975475ca57e",
    "id": "192620a2-c8a1-45bd-8809-785e1a27f4cb",
    "name": "deutscher name",
    "description": "deutsche beschreibung",
    "locale": "de"
  },
  "translationLocales": [
    "de",
    "en"
  ]
}

I'm not sure about if I missed something or it's just the way it works :). My expectation was to get something like the shown json but without these doubles. Additionally on extending the request with ?locale=de I still get the whole response shown up here. Any hints for me so far without extracting the whole codebase here?

I am using API-Platform 3.1.17 and with Doctrine ORM 2.16.2.

Automatically add translation when it does not exist

Hello,

I have an entity without any translation and i see in my Symfony profiler multiple requests to query current locale, and default locale, then if no translation, insert new translation.

The problem is i don't want to add new translation automatically.

I think, when i call $this->getTranslations('unavailable locale')->getName(), i have a new translation object created with name = null but with a $entityManager->flush() for other reasons after this getTranslations(), this object is persisted to database.

This is my error:
An exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column \"template\" of relation \"email_translation\" violates not-null constraint\nDETAIL: Failing row contains (1ecd36ca-d8ef-6572-bb2a-c1eda6aadb28, 1ecd23cc-a924-61aa-8575-d1e3b0741a73, fr, null).

OrderFilter on translated fields and a certain locale

Suppose I have an Entity with a translated field name.

Could you please suggest a solution to the situation when I need to order the items collection (using OrderFilter) by the name field considering the current locale? What should the annotations look like?

Filters on translated fields

Seems that SearchFilter doesn't work out of the box. Do you have any suggestion on how to make it work? Ideally it should be as easy as this:

/products?locale=es&name=foo

where name is a translated field.

Support for PHP8

First of all thanks for this extension, as it saved me a lot of time.
It would be cool to support PHP8 now that's been released. I tried the extension with ignore-platform-reqs and it seems to work correctly

How to use by example? I'm receive error.

Hi
How to use by examples from README?

My files:
App\Entity\Post

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;

/**
 * @ApiResource(
 *     itemOperations={
 *         "get"={"normalization_context"={"groups"={"get"}}},
 *         "put"={"normalization_context"={"groups"={"translations"}}}
 *     },
 *     collectionOperations={
 *         "get",
 *         "post"={"normalization_context"={"groups"={"translations"}}}
 *    },
 *    normalizationContext={"groups"={"post_read"}},
 *    denormalizationContext={"groups"={"post_write"}}
 * )
 * @ORM\Entity
 */
class Post extends AbstractTranslatable
{
    /**
     * @var int
     *
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @Groups({"post_read"})
     */
    private $title;

    /**
     * @Groups({"post_write", "translations"})
     */
    protected $translations;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->getTranslation()->setTitle($title);

        return $this;
    }

    public function getTitle()
    {
        return $this->getTranslation()->getTitle();
    }

    /**
     * Create resource translation model.
     *
     * @return TranslationInterface
     */
    protected function createTranslation(): TranslationInterface
    {
        return new PostTranslation();
    }
}

App\Entity\PostTranslation

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;

/**
 * @ORM\Entity
 */
class PostTranslation extends AbstractTranslation
{
    /**
     * @var int
     *
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column
     * @Groups({"post_read", "post_write", "translations"})
     */
    private $title;

    /**
     * @var string
     * @ORM\Column
     * @Groups({"post_write", "translations"})
     */
    protected $locale;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    public function getTitle()
    {
        return $this->title;
    }
}

After send POST to /api/posts

{
	"translations": {
		"ru": {
			"title":"Some title"
		},
		"uk": {
			"title":"Some title"
		}
	}
}

I'm receive error:

{
    "@context": "/api/contexts/Error",
    "@type": "hydra:Error",
    "hydra:title": "An error occurred",
    "hydra:description": "Could not denormalize object of type Locastic\\ApiPlatformTranslationBundle\\Model\\TranslationInterface[], no supporting normalizer found.",
     ...
}

Cannot get it to work

whatever I try to do I always get the error:

"Argument 1 passed to Locastic\ApiPlatformTranslationBundle\EventListener\AssignLocaleListener::prePersist() must be an instance of Doctrine\Common\Persistence\Event\LifecycleEventArgs, instance of Doctrine\ORM\Event\LifecycleEventArgs given, called in C:\xampp\htdocs\Symfony\project_4\vendor\symfony\doctrine-bridge\ContainerAwareEventManager.php on line 58"

I followed the readme and uses two different json formats to post. both resulting in the error above.

{
  "translations": [
    {
      "title": "test",
      "locale": "en"
    }
  ]
}
{
    "translations": { 
        "en":{
            "title":"test",
            "locale":"en"
        },
        "de":{
            "title":"test de",
            "locale":"de"
        }
    }
}

Here are my entity's:
Event.php

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\EventRepository;
use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ApiResource(
 *     itemOperations={
 *         "get"={"normalization_context"={"groups"={"get"}}},
 *         "put"={"normalization_context"={"groups"={"translations"}}}
 *     },
 *     collectionOperations={
 *         "get",
 *         "post"={"normalization_context"={"groups"={"translations"}}}
 *    },
 *    normalizationContext={"groups"={"post_read"}},
 *    denormalizationContext={"groups"={"post_write"}}
 * )
 * @ORM\Entity(repositoryClass=EventRepository::class)
 */
class Event extends AbstractTranslatable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @Groups({"post_read"})
     */
    private $title;

    /**
     * @ORM\OneToMany(targetEntity="EventTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     *
     * @Groups({"post_write", "translations"})
     */
    protected $translations;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->getTranslation()->getLocale();
    }

    public function setTitle(string $title): void
    {
        $this->getTranslation()->setLocale($title);
    }

    protected function createTranslation(): TranslationInterface
    {
        return new EventTranslation();
    }
}

EventTranslation.php

<?php

namespace App\Entity;

use App\Repository\EventTranslationRepository;
use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ORM\Entity(repositoryClass=EventTranslationRepository::class)
 */
class EventTranslation extends AbstractTranslation
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Event", inversedBy="translations")
     */
    protected $translatable;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Groups({"post_read", "post_write", "translations"})
     */
    private $title;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Groups({"post_write", "translations"})
     */
    protected  $locale;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }
}

inconsistency of returning types

there is an inconsistency of returning types when translation is empty it's returning array instead of object

no translations

{
"id":1,
 "translations": []
}

there are translations

{
"id":1,
 "translations": { 
        "en":{
            "title":"test",
            "content":"test",
            "locale":"en"
        },
        "de":{
            "title":"test de",
            "content":"test de",
            "locale":"de"
        }
    }
}

maybe it's should return {} or null if there are no translations

Error on loading AliceBundleFixtures

I have an issue on loading AliceBundleFixtures for translations managed by ApiPlatformTranslationBundle.

This is the trace on loading fixtures:

In SimpleObjectGenerator.php line 111:
                                                                                                                                                                                                         
  An error occurred while generating the fixture "article_1_inventory2" (App\Entity\ArticleInventory): An error occurred while generating the fixture "article_color_blue_en" (App\Entity\ArticleColorT  
  ranslation): Call to a member function containsKey() on null                                                                                                                                           
                                                                                                                                                                                                         

In SimpleObjectGenerator.php line 111:
                                                                                                                                                                
  An error occurred while generating the fixture "article_color_blue_en" (App\Entity\ArticleColorTranslation): Call to a member function containsKey() on null  
                                                                                                                                                                

In TranslatableTrait.php line 146:
                                                   
  Call to a member function containsKey() on null  
                                                   

This is my fixtures:

App\Entity\ArticleColorTranslation:
    article_color_black_fr:
        translatable: '@article_color_black'
        locale: 'fr'
        name: 'Noir'
    article_color_black_en:
        translatable: '@article_color_black'
        locale: 'en'
        name: 'Black'
    article_color_white_fr:
        translatable: '@article_color_white'
        locale: 'fr'
        name: 'Blanc'
    article_color_white_en:
        translatable: '@article_color_white'
        locale: 'en'
        name: 'White'
    article_color_red_fr:
        translatable: '@article_color_red'
        locale: 'fr'
        name: 'Rouge'
    article_color_red_en:
        translatable: '@article_color_red'
        locale: 'en'
        name: 'Red'
    article_color_blue_fr:
        translatable: '@article_color_blue'
        locale: 'fr'
        name: 'Bleu'
    article_color_blue_en:
        translatable: '@article_color_blue'
        locale: 'en'
        name: 'Blue'
    article_color_yellow_fr:
        translatable: '@article_color_yellow'
        locale: 'fr'
        name: 'Jaune'
    article_color_yellow_en:
        translatable: '@article_color_yellow'
        locale: 'en'
        name: 'Yellow'
    article_color_green_fr:
        translatable: '@article_color_green'
        locale: 'fr'
        name: 'Vert'
    article_color_green_en:
        translatable: '@article_color_green'
        locale: 'en'
        name: 'Green'

Thanks for your help..

Post requests failing on a fresh install

I just installed Locastic Translation Bundle in a new API Platform Symfony project. Everything works fine except POST requests, that return me an error.

I followed the detailed guide in the Readme and created my translatable entity, and setup the Locale.

I can translate existing content using PATCH requests, GET requests acknoledge my locale correctly and provide me translated informations in the right language with no error, so I think everything is setup correctly.

The error that POST requests return :

Return value of Locastic\\ApiPlatformTranslationBundle\\Model\\AbstractTranslatable::getTranslations() must implement interface Doctrine\\Common\\Collections\\Collection, null returned

"file": "C:\\Users\\user\\dev\\symfony-project\\vendor\\locastic\\api-platform-translation-bundle\\src\\Model\\TranslatableTrait.php",
"line": 138,

Here's my entity ( Challenge.php )

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Repository\ChallengeRepository;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;

/**
 * @ApiResource(
 *      attributes={
 *              "filters"={"translation.groups"},
 *              "normalization_context"={"groups"={"chall:read"}},
 *              "denormalization_context"={"groups"={"chall:write"}}
 *      },
 *      collectionOperations={
 *         "get",
 *         "post"={
 *             "normalization_context"={"groups"={"translations"}}
 *         }
 *      },
 *      itemOperations={
 *         "get",
 *         "put"={
 *              "normalization_context"={"groups"={"translations"}}
 *          },
 *         "delete"
 *      }
 * )
 * @ORM\Entity(repositoryClass=ChallengeRepository::class)
 */
class Challenge extends AbstractTranslatable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"chall:read"})
     */
    private $title;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"chall:read", "chall:write"})
     */
    private $code;

    /**
     * @ORM\Column(type="text")
     * @Groups({"chall:read"})
     */
    private $description;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     * @Groups({"chall:read", "chall:write"})
     */
    private $startAt;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     * @Groups({"chall:read", "chall:write"})
     */
    private $endAt;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     * @Groups({"chall:read", "chall:write"})
     */
    private $registryLimitDate;

    /**
     * @ORM\Column(type="integer", nullable=true)
     * @Groups({"chall:read", "chall:write"})
     */
    private $maxSlots;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Groups({"chall:read", "chall:write"})
     */
    private $image;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Groups({"chall:read", "chall:write"})
     */
    private $status;

    /**
     * @ORM\ManyToMany(targetEntity=Course::class, mappedBy="challenges")
     * @Groups({"chall:read", "chall:write"})
     */
    private $courses;

    /**
     * @ORM\OneToMany(targetEntity="ChallengeTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     * @Groups({"chall:write", "translations"})
     */
    protected $translations;

    public function __construct()
    {
        $this->courses = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }

    public function setTitle(string $title): self
    {
        $this->getTranslation()->setTitle($title);

        return $this;
    }

    public function getCode(): ?string
    {
        return $this->code;
    }

    public function setCode(string $code): self
    {
        $this->code = $code;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->getTranslation()->getDescription();
    }

    public function setDescription(string $description): self
    {
        $this->getTranslation()->setDescription($title);

        return $this;
    }

    public function getStartAt(): ?\DateTimeInterface
    {
        return $this->startAt;
    }

    public function setStartAt(?\DateTimeInterface $startAt): self
    {
        $this->startAt = $startAt;

        return $this;
    }

    public function getEndAt(): ?\DateTimeInterface
    {
        return $this->endAt;
    }

    public function setEndAt(?\DateTimeInterface $endAt): self
    {
        $this->endAt = $endAt;

        return $this;
    }

    public function getRegistryLimitDate(): ?\DateTimeInterface
    {
        return $this->registryLimitDate;
    }

    public function setRegistryLimitDate(?\DateTimeInterface $registryLimitDate): self
    {
        $this->registryLimitDate = $registryLimitDate;

        return $this;
    }

    public function getMaxSlots(): ?int
    {
        return $this->maxSlots;
    }

    public function setMaxSlots(?int $maxSlots): self
    {
        $this->maxSlots = $maxSlots;

        return $this;
    }

    public function getImage(): ?string
    {
        return $this->image;
    }

    public function setImage(?string $image): self
    {
        $this->image = $image;

        return $this;
    }

    public function getStatus(): ?string
    {
        return $this->status;
    }

    public function setStatus(?string $status): self
    {
        $this->status = $status;

        return $this;
    }

    /**
     * @return Collection|Course[]
     */
    public function getCourses(): Collection
    {
        return $this->courses;
    }

    public function addCourse(Course $course): self
    {
        if (!$this->courses->contains($course)) {
            $this->courses[] = $course;
            $course->addChallenge($this);
        }

        return $this;
    }

    public function removeCourse(Course $course): self
    {
        if ($this->courses->contains($course)) {
            $this->courses->removeElement($course);
            $course->removeChallenge($this);
        }

        return $this;
    }

    protected function createTranslation(): TranslationInterface
    {
        return new ChallengeTranslation();
    }
}

And my translated entity ( ChallengeTranslation.php )

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Repository\ChallengeTranslationRepository;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;

/**
 * @ORM\Entity(repositoryClass=ChallengeTranslationRepository::class)
 */
class ChallengeTranslation extends AbstractTranslation
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Challenge", inversedBy="translations")
     */
    protected $translatable;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"chall:read", "chall:write", "translations"})
     */
    private $title;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"chall:read", "chall:write", "translations"})
     */
    private $description;

    /**
     * @ORM\Column(type="string")
     * @Groups({"chall:write", "translations"})
     */
    protected $locale;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(string $description): self
    {
        $this->description = $description;

        return $this;
    }
}

Is this a common issue ? A little help would be much appreciated as almost everything is working !

support api-platform/core^3.0@rc

locastic/api-platform-translation-bundle[v1.3, ..., v1.3.4] require api-platform/core ^2.1 -> found api-platform/core[v2.1.0, ..., v2.7.0-rc.2] but it conflicts with your root composer.json require (^3.0@rc).

New release?

Hello,
Do you have a release date for a new version? Especially for the last fix 09437e2

Can't force certain locale

I am using this bundle but I have a problem that it is translating entity to locale set in Request, which is not the desired behaviour.

For example I don't have translations for en locale, I only have them for 'sl', but with request with header accept-language: en,sl empty translations are returned even though default locale is set to 'sl'.

Is there a way to configure the bundle to only use predefined pool of locales (for example: sl, de), so it will ignore request locale but always use fixed one?

The Symfony validator don't with this bundle

Hello ,
I tried to use Constraints such as : length or NotBlank
but it seems be ignored

AreaTranslation.php

/**
 * @ORM\Entity()
 */
class AreaTranslation extends AbstractTranslation
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"Area_read","Area_write"})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Area", inversedBy="translations")
     * @Assert\NotNull
     */
    protected $translatable;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank()
     * @Assert\Length(max=1500)
     * @Groups({"Area_read","Area_write","translations"})
     */
    private $name;

    /**
     * @ORM\Column(type="string")
     * @Assert\NotBlank
     * @Assert\Language
     * @Groups({"Area_read","Area_write","translations"})
     */
    protected $locale;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }
}

Force length 2 for locales

Hello,

Is it possible to force locale have length of 2 chars: instead of fr_FR, i want only fr ?

Rédoine

Not null violation

hello ,
i use symfony 4.4 and posgresql , but when i try to insert a new value using POST method , I get this error :

"hydra:description": "An exception occurred while executing 'INSERT INTO tag (id, title) VALUES (?, ?)' with params [5, null]:\n\nSQLSTATE[23502]: Not null violation

Tag.php

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use App\Entity\Translation\TagTranslation;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Filter\FullTextSearchFilter;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\TagRepository")
 * @ApiResource(
 *      attributes={
 *              "filters"={"translation.groups"},
 *              "normalization_context"={"groups"={"Tag_read"}},
 *              "denormalization_context"={"groups"={"Tag_write"}}
 *      },
 *      collectionOperations={
 *         "get",
 *         "post"={
 *             "normalization_context"={"groups"={"translations"}}
 *         }
 *      },
 *      itemOperations={
 *         "get",
 *         "put"={
 *              "normalization_context"={"groups"={"translations"}}
 *          },
 *         "delete"
 *      }
 * )
 */
class Tag extends AbstractTranslatable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"Category_read","Tag_read","Image_read"})
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255,unique=true)
     * @Groups({"Category_read","Tag_read","Image_read"})
     */
    private $title;

    /**
     * @ORM\ManyToMany(targetEntity=Image::class)
     * @Groups({"Tag_read"})
     */
    private $images;

    /**
     * @ORM\ManyToMany(targetEntity=Category::class, mappedBy="tags")
     * @Groups({"Tag_read"})
     */
    private $categories;


    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Translation\TagTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     * @Groups({"Tag_write", "translations"})
     */
    protected $translations;

    public function __construct()
    {
        parent::__construct();
        $this->images = new ArrayCollection();
        $this->categories = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }

    public function setTitle(string $title)
    {
        $this->getTranslation()->setTitle($title);
    }

    /**
     * @return Collection|Image[]
     */
    public function getImages(): Collection
    {
        return $this->images;
    }

    public function addImage(Image $image): self
    {
        if (!$this->images->contains($image)) {
            $this->images[] = $image;
        }

        return $this;
    }

    public function removeImage(Image $image): self
    {
        if ($this->images->contains($image)) {
            $this->images->removeElement($image);
        }

        return $this;
    }

    /**
     * @return Collection|Category[]
     */
    public function getCategories(): Collection
    {
        return $this->categories;
    }

    public function addCategory(Category $category): self
    {
        if (!$this->categories->contains($category)) {
            $this->categories[] = $category;
            $category->addTag($this);
        }

        return $this;
    }

    public function removeCategory(Category $category): self
    {
        if ($this->categories->contains($category)) {
            $this->categories->removeElement($category);
            $category->removeTag($this);
        }

        return $this;
    }

    protected function createTranslation(): TranslationInterface
    {
        return new TagTranslation();
    }
}

and TagTranslation.php

<?php

namespace App\Entity\Translation;

use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ORM\Entity()
 */
class TagTranslation extends AbstractTranslation
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Tag", inversedBy="translations")
     */
    protected $translatable;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Groups({"Tag_read","Tag_write"})
     */
    private $title;

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"Tag_write", "translations"})
     */
    protected $locale;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }
}

Is this a common issue ?

Api pack dependency

Is there any particular reason why this library depends on api-platform/api-pack instead of api-platform/core?

PUT requests not updating translations, they always INSERT and DELETE all of them

I'm having a bit of trouble trying to edit translations.
It seems every time I make a PUT request to my resource to update one translation it begins a new database transaction, inserts new items and then deletes every translation item in the table.

I've followed the guide and here are my entities:

Product.php

/**
 * @ApiResource(
 *      collectionOperations={
 *          "get"
 *      },
 *      itemOperations={
 *          "get",
 *          "put"={
 *              "security"="is_granted('ROLE_ADMIN')",
 *              "normalizationContext"={"groups"={"translations"}}
 *          },
 *          "delete"={"security"="is_granted('ROLE_ADMIN')"}
 *      },
 *      normalizationContext={"groups"={"product.read"}},
 *      denormalizationContext={"groups"={"product.write"}}
 * )
 * @ORM\Entity(repositoryClass=ProductRepository::class)
 * @ORM\Table("products")
 * @ORM\HasLifecycleCallbacks
 */
class Product extends AbstractTranslatable
{
    /* more properties */
    
    /**
     * @ORM\OneToMany(
     *      targetEntity=ProductTranslation::class,
     *      mappedBy="translatable",
     *      fetch="EXTRA_LAZY",
     *      indexBy="locale",
     *      cascade={"persist"},
     *      orphanRemoval=true
     * )
     *
     * @Groups({"product.read", "product.write", "translations"})
     */
    protected $translations;

    private string $title;
    
    public function __construct()
    {
        $this->translations = new ArrayCollection();
    }
    
    protected function createTranslation(): TranslationInterface
    {
        return new ProductTranslation();
    }

    public function setTitle(string $title)
    {
        $this->getTranslation()->setTitle($title);
    }

    public function getTitle(): ?string
    {
        return $this->getTranslation()->getTitle();
    }   
}

ProductTranslation.php

/**
 * @ORM\Entity(repositoryClass=ProductTranslationRepository::class)
 * @ORM\Table("products_translations")
 */
class ProductTranslation extends AbstractTranslation
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"product.read", "product.write", "translations"})
     */
    private int $id;

    /**
     * @ORM\ManyToOne(targetEntity=Product::class, inversedBy="translations")
     */
    protected $translatable;

    /**
     * @ORM\Column(type="string")
     * @Groups({"product.read", "product.write", "translations"})
     */
    private $title;

    /**
     * @ORM\Column(type="string")
     * @Groups({"product.read", "product.write", "translations"})
     */
    protected $locale;

    public function getId(): ?int
    {
        return $this->id;
    }
    
    public function setTitle(string $title): void
    {
        $this->title = $title;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }
}

TranslationInterface, no supporting normalizer found.

I've got a fresh template form https://github.com/api-platform/api-platform
and I got
api/config/api_platform/resources.yaml

resources:
    App\Entity\Event:
        itemOperations:
            get:
                method: GET
            put:
                method: PUT
                normalization_context:
                    groups: [ 'translations' ]
        collectionOperations:
            get:
                method: GET
            post:
                method: POST
                normalization_context:
                    groups: [ 'translations' ]
        attributes:
            filters: [ 'translation.groups' ]
            normalization_context:
                groups: [ 'event_read' ]
            denormalization_context:
                groups: [ 'event_write' ]
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * Class Event
 * @package App\Entity
 * @ORM\Entity
 */
class Event extends AbstractTranslatable
{
    /**
     * @var int The entity Id
     *
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @Groups({"event_read", "event_write"})
     * @ORM\Column
     */
    private $startAt;

    /**
     * @Groups({"event_read"})
     * @ORM\Column
     */
    private $title;

    /**
     * @Groups({"event_read"})
     * @ORM\Column
     */
    private $description;

    /**
     * @Groups({"event_write", "translations"})
     */
    protected $translations;

    public function getId()
    {
        return $this->id;
    }

    public function getStartAt(): \DateTime
    {
        return $this->startAt;
    }

    public function setStartAt(\DateTime $startAt)
    {
        $this->startAt = $startAt;
    }

    public function setTitle($title)
    {
        $this->getTranslation()->setTitle($title);
    }

    public function getTitle(): string
    {
        return $this->getTranslation()->getTitle();
    }

    public function setDescription($description)
    {
        $this->getTranslation()->setDescription($description);
    }

    public function getDescription(): string
    {
        return $this->getTranslation()->getDescription();
    }

    protected function createTranslation():TranslationInterface
    {
        return new EventTranslation();
    }
}
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;
use Symfony\Component\Serializer\Annotation\Groups;


/**
 * Class EventTranslation
 * @package App\Entity
 * @ORM\Entity
     */
class EventTranslation extends AbstractTranslation
{
    /**
     * @var int The entity Id
     *
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column
     * @Groups({"event_read", "event_write", "translations"})
     */
    private $title;

    /**
     * @var string
     * @ORM\Column
     * @Groups({"event_read", "event_write", "translations"})
     */
    private $description;

    /**
     * @var string
     *
     * @ORM\Column
     * @Groups({"event_write", "translations"})
     */
    protected $locale;

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @param int $id
     */
    public function setId(int $id): void
    {
        $this->id = $id;
    }

    /**
     * @return string
     */
    public function getTitle(): string
    {
        return $this->title;
    }

    /**
     * @param string $title
     */
    public function setTitle(string $title): void
    {
        $this->title = $title;
    }

    /**
     * @return string
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    /**
     * @param string $description
     */
    public function setDescription(string $description): void
    {
        $this->description = $description;
    }

    /**
     * @return string
     */
    public function getLocale(): string
    {
        return $this->locale;
    }

    /**
     * @param string $locale
     */
    public function setLocale(?string $locale): void
    {
        $this->locale = $locale;
    }
}

got an errror

"hydra:description": "Could not denormalize object of type \"Locastic\\ApiPlatformTranslationBundle\\Model\\TranslationInterface[]\", no supporting normalizer found.",

well I try sec time

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;

/**
 * @ApiResource(
 *     itemOperations={
 *         "get"={"normalization_context"={"groups"={"get"}}},
 *         "put"={"normalization_context"={"groups"={"translations"}}}
 *     },
 *     collectionOperations={
 *         "get",
 *         "post"={"normalization_context"={"groups"={"translations"}}}
 *    },
 *    normalizationContext={"groups"={"post_read"}},
 *    denormalizationContext={"groups"={"post_write"}}
 * )
 * @ORM\Entity
 */
class Post extends AbstractTranslatable
{
    /**
     * @var int
     *
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(type="string")
     * @Groups({"post_read"})
     */
    private $title;

    /**
     * @Groups({"post_write", "translations"})
     */
    protected $translations;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->getTranslation()->setTitle($title);

        return $this;
    }

    public function getTitle()
    {
        return $this->getTranslation()->getTitle();
    }

    /**
     * Create resource translation model.
     *
     * @return TranslationInterface
     */
    protected function createTranslation(): TranslationInterface
    {
        return new PostTranslation();
    }
}
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;

/**
 * @ORM\Entity
 */
class PostTranslation extends AbstractTranslation
{
    /**
     * @var int
     *
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(type="string")
     * @Groups({"post_read", "post_write", "translations"})
     */
    private $title;

    /**
     * @var string
     * @ORM\Column(type="string")
     * @Groups({"post_write", "translations"})
     */
    protected $locale;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function __toString()
    {
        return $this->title;
    }
}

got the same error

No locale has been set and current locale is undefined.

Hello,

All is working good for view and edit operations but i can not create, i have this error : No locale has been set and current locale is undefined.

My entity

<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;

/**
 * @ApiResource(
 *      attributes={
 *          "security"="is_granted('ROLE_USER')",
 *          "security_message"="You must be connected !",
 *          "filters"={"translation.groups"},
 *      },
 *       itemOperations={
 *          "get"={
 *              "method"="GET",
 *              "normalization_context"={"groups":{"application:read", "translations"}}
 *          },
 *          "put"={
 *              "method"="PUT",
 *              "normalization_context"={"groups":{"translations"}}
 *          }
 *      },
 *      collectionOperations={
 *          "applications_by_username"={
 *              "method"="GET",
 *              "name"="applications_by_username",
 *              "controller"=GetApplicationsByUsernameController::class
 *          },
 *          "post"={
 *              "method"="POST",
 *              "normalization_context"={"groups":{"translations"}}
 *          }
 *      })
 * @ORM\Entity(repositoryClass="App\Repository\ApplicationRepository")
 * @ORM\HasLifecycleCallbacks
 *
 */
class Application extends AbstractTranslatable
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     *
     *  @var int
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"application:read","translations"})
     *
     *  @var string
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\ApplicationTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"persist"}, orphanRemoval=true)
     * @Groups({"translations"})
     *
     * @var Collection
     */
    protected $translations;

    public function __construct()
    {
        parent::__construct();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->getTranslation()->getName();
    }

    public function setName(string $name): void
    {
        $this->getTranslation()->setName($name);
    } 

    public function createTranslation(): TranslationInterface
    {
        return new ApplicationTranslation();
    }

    public function __toString()
    {
        return $this->getName();
    }
}

My translation entity

<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;
use Locastic\ApiPlatformTranslationBundle\Model\TranslatableInterface;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ApiResource()
 * @ORM\Entity(repositoryClass="App\Repository\ApplicationTranslationRepository")
 */
class ApplicationTranslation extends AbstractTranslation
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     *
     * @var int
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"application:read","translations"})
     *
     * @var string
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"application:read","translations"})
     *
     * @var null|string
     */
    protected $locale;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Application", inversedBy="translations")
     *
     * @var TranslatableInterface
     */
    protected $translatable;

    public function __construct()
    {
        parent::__construct();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function __toString()
    {
        return $this->getName();
}

translation.yaml

framework:
    default_locale: fr
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - fr

Stacktrace
RuntimeException:
No locale has been set and current locale is undefined.

at vendor/locastic/api-platform-translation-bundle/src/Model/TranslatableTrait.php:65
at Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable->getTranslation()
(src/Entity/Application.php:192)
at App\Entity\Application->getName()
(vendor/symfony/property-access/PropertyAccessor.php:405)
at Symfony\Component\PropertyAccess\PropertyAccessor->readProperty(array(object(Application)), 'name', false)
(vendor/symfony/property-access/PropertyAccessor.php:329)
at Symfony\Component\PropertyAccess\PropertyAccessor->readPropertiesUntil(array(object(Application)), object(PropertyPath), 1, true)
(vendor/symfony/property-access/PropertyAccessor.php:231)
at Symfony\Component\PropertyAccess\PropertyAccessor->isReadable(object(Application), object(PropertyPath))
(vendor/easycorp/easyadmin-bundle/src/Field/Configurator/CommonPreConfigurator.php:89)
at EasyCorp\Bundle\EasyAdminBundle\Field\Configurator\CommonPreConfigurator->buildValueOption(object(FieldDto), object(EntityDto))
(vendor/easycorp/easyadmin-bundle/src/Field/Configurator/CommonPreConfigurator.php:41)
at EasyCorp\Bundle\EasyAdminBundle\Field\Configurator\CommonPreConfigurator->configure(object(FieldDto), object(EntityDto), object(AdminContext))
(vendor/easycorp/easyadmin-bundle/src/Factory/FieldFactory.php:87)
at EasyCorp\Bundle\EasyAdminBundle\Factory\FieldFactory->processFields(object(EntityDto), object(FieldCollection))
(vendor/easycorp/easyadmin-bundle/src/Factory/EntityFactory.php:43)
at EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory->processFields(object(EntityDto), object(FieldCollection))
(vendor/easycorp/easyadmin-bundle/src/Controller/AbstractCrudController.php:276)
at EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController->new(object(AdminContext))
(vendor/symfony/http-kernel/HttpKernel.php:157)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/http-kernel/HttpKernel.php:79)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/http-kernel/Kernel.php:196)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(public/index.php:25)

Any idea please ?

Missing docs for a programmatic example

I'm looking for a programmatic example in order to add new translations e.g. using a command, but I couldn't figure out how to get started. I'd be glad if you could give me a hint on how to start or extend the documentation with an example. Thanks!

No locale has been set and current locale is undefined.

I did everything according to this instruction
https://github.com/Locastic/ApiPlatformTranslationBundle

And i get error after post method:
{ "@context": "/api/contexts/Error", "@type": "hydra:Error", "hydra:title": "An error occurred", "hydra:description": "No locale has been set and current locale is undefined.", "trace": [ { "namespace": "", "short_class": "", "class": "", "type": "", "function": "", "file": "/site/vendor/locastic/api-platform-translation-bundle/src/Model/TranslatableTrait.php", "line": 64, "args": [] }, .... }
(I use symfony5)

editing object after increment 'Translation' id

Hello

I have a big problem

cant detect Translation object edit event because

run: insert event

image

why insert if already exist?

can i change this update?

symfony: 5.4.3
api-platform: 2.6.8

request body

{
    "@context": "/contexts/EmailTemplate",
    "@id": "/email_templates/1",
    "@type": "EmailTemplate",
    "id": 1,
    "key": "confirmation",
    "module": "admin",
    "translations": {
        "hu": {
            "@type": "EmailTemplateTranslation",
            "@id": "_:3955",
            "id": 29,
            "locale": "hu",
            "name": "email_template.confirmation.name",
            "subject": "email_template.confirmation.subject",
            "content": "<p>email_template.confirmation.content33333</p>",
            "tags": []
        },
        "en": {
            "@type": "EmailTemplateTranslation",
            "@id": "_:3957",
            "id": 30,
            "locale": "en",
            "name": "email_template.confirmation.name",
            "subject": "email_template.confirmation.subject",
            "content": "email_template.confirmation.content",
            "tags": []
        }
    },
    "name": "email_template.confirmation.name",
    "subject": "email_template.confirmation.subject",
    "content": "<p>email_template.confirmation.content22222</p>"
}

image
image

Doctrine/orm 3 support

Dear maintainers,

could we kindly request the inclusion of support for doctrine/orm 3? @konradkozaczenko has already submitted GH-68. Doctrine/orm 3 requires PHP 8.1+, so this release may require updating the package.json to reflect the change. Given the substantial implications, I think the release with this updates must be major version.

Best regards!

Uncaught Error: Return value of Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable::getTranslations() must implement interface Doctrine\Common\Collections\Collection, null returned

hello.
I have a fresh installation of the api platform with locastic translation bundle.
After setting up the Entity and Translation entity when I do a POST request to create a new book I get this error:
Uncaught Error: Return value of Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable::getTranslations() must implement interface Doctrine\Common\Collections\Collection, null returned

My Book.php

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Repository\BookRepository;
use Doctrine\Common\Collections\Collection;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslatable;
use Locastic\ApiPlatformTranslationBundle\Model\TranslationInterface;

/**
 * @ApiResource(
 *  attributes={"filters"={"translation.groups"}},
 *  normalizationContext={"groups"={"book:read"}, "swagger_definition_name"="Read"},
 *  denormalizationContext={"groups"={"book:write"}, "swagger_definition_name"="Write"},
 * itemOperations={
 *    "get",
 *    "put"={
 *      "normalization_context"={"groups"={"translations"}}
 *    }
 *  },
 *  collectionOperations={
 *    "get",
 *    "post"={
 *      "normalization_context"={"groups"={"translations"}}
 *    },
 *  }
 * )
 * @ORM\Entity(repositoryClass=BookRepository::class)
 */
class Book extends AbstractTranslatable
{
  /**
   * @ORM\Id
   * @ORM\GeneratedValue
   * @ORM\Column(type="integer")
   * 
   * @Groups({"book:read"})
   */
  private $id;

  /**
   * @ORM\Column(type="string", length=255)
   * 
   * @Groups({"book:read"})
   */
  private $title;

  /**
   * @ORM\Column(type="text")
   * 
   * @Groups({"book:read"})
   */
  private $description;

  /**
   * @ORM\OneToMany(targetEntity="BookTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
   *
   * @Groups({"book:write"})
   * @Groups({"translations"})
   */
  protected $translations;

  public function __construct()
  {
    $this->translations= new ArrayCollection();
  }

  public function getId(): ?int
  {
    return $this->id;
  }

  public function setTitle(string $title)
  {
    $this->getTranslation()->setTitle($title);
  }

  public function getTitle(): ?string
  {
    return $this->getTranslation()->getTitle();
  }

  public function setDescription(string $description)
  {
    $this->getTranslation()->setDescription($description);
  }

  public function getDescription(): ?string
  {
    return $this->getTranslation()->getDescription();
  }

  protected function createTranslation(): TranslationInterface
  {
    return new BookTranslation();
  }
}

My BookTranslation.php

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Locastic\ApiPlatformTranslationBundle\Model\AbstractTranslation;

/**
 * @ORM\Entity()
 */
class BookTranslation extends AbstractTranslation
{
  /**
   * @ORM\Id
   * @ORM\GeneratedValue
   * @ORM\Column(type="integer")
   */
  private $id;

  /**
   * @ORM\ManyToOne(targetEntity="Book", inversedBy="translations")
   */
  protected $translatable;

  /**
   * @ORM\Column(type="string")
   * 
   * @Groups({"book:read", "book:write", "translations"})
   */
  private $title;

  /**
   * @ORM\Column(type="text")
   * 
   * @Groups({"book:read", "book:write", "translations"})
   */
  private $description;

  /**
   * @ORM\Column(type="string")
   *
   * @Groups({"book:write", "translations"})
   */
  protected $locale;

  public function setTitle(string $title): void
  {
    $this->title = $title;
  }

  public function getTitle(): ?string
  {
    return $this->title;
  }

  public function setDescription(string $description): void
  {
    $this->description = $description;
  }

  public function getDescription(): ?string
  {
    return $this->description;
  }
}
```

PHP version: `PHP 7.4.9 (cli) (built: Aug  4 2020 11:52:41) ( ZTS Visual C++ 2017 x64 )`
MySQL version: `MySQL 8.0.21`

Why does this is caused?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.