Locastic / ApiPlatformTranslationBundle

Translation bundle for ApiPlatform based on Sylius translation
MIT License
84 stars 28 forks source link

TranslationInterface, no supporting normalizer found. #30

Closed yalagin closed 3 years ago

yalagin commented 3 years ago

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

paullla commented 3 years ago

Hi @yalagin, there are a few things you should change in your code:

  1. Event entity should not map name and description to the database, those fields are meant to be saved only in EventTranslation.
  2. You should map translations field in Event:
    /**
     * @ORM\OneToMany(targetEntity="EventTranslation", mappedBy="translatable", fetch="EXTRA_LAZY", indexBy="locale", cascade={"PERSIST"}, orphanRemoval=true)
     * @Groups({"event_write", "translations"})
     */
    protected $translations;
  3. And map translatable field in EventTranslation:
    /**
     * @ORM\ManyToOne(targetEntity="Event", inversedBy="translations")
     */
    protected $translatable;

So the final result would be:

<?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"})
     */
    private $title;

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

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

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

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

    public function setStartAt($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 Locastic\ApiPlatformTranslationBundle\Model\TranslatableInterface;
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;

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

    /**
     * @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;
    }
}

Example request:

{
  "startAt": "2021-10-10",
  "translations": {
    "en":{
      "title": "Test title",
      "description": "Test description",
      "locale": "en"
   }
  }
}

Please let me know if this works for you.

PS. Make sure you update the bundle to the latest version, we just released some upgrades :)

yalagin commented 3 years ago

Thanks it's working now =) I already implemented another approach in last project but for current one I am using your bundle.