PHP code example of janwebdev / translatable-entity-bundle

1. Go to this page and download the library: Download janwebdev/translatable-entity-bundle library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

janwebdev / translatable-entity-bundle example snippets



// config/bundles.php

return [
    // ...
    Janwebdev\TranslatableEntityBundle\TranslatableEntityBundle::class => ['all' => true],
];



namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Janwebdev\TranslatableEntityBundle\Model\TranslatableWrapper;

#[ORM\Entity]
#[ORM\Table(name: 'article')]
class Article extends TranslatableWrapper
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    /**
     * @var Collection<int, ArticleTranslation>
     */
    #[ORM\OneToMany(
        targetEntity: ArticleTranslation::class,
        mappedBy: 'translatable',
        cascade: ['persist', 'remove'],
        orphanRemoval: true
    )]
    private Collection $translations;

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

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

    /**
     * @return Collection<int, ArticleTranslation>
     */
    public function getTranslations(): Collection
    {
        return $this->translations;
    }

    public function addTranslation(ArticleTranslation $translation): self
    {
        if (!$this->translations->contains($translation)) {
            $this->translations->add($translation);
            $translation->setTranslatable($this);
        }

        return $this;
    }

    public function removeTranslation(ArticleTranslation $translation): self
    {
        if ($this->translations->removeElement($translation)) {
            if ($translation->getTranslatable() === $this) {
                $translation->setTranslatable(null);
            }
        }

        return $this;
    }
}



namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Janwebdev\TranslatableEntityBundle\Model\TranslatingInterface;

#[ORM\Entity]
#[ORM\Table(name: 'article_translation')]
#[ORM\UniqueConstraint(name: 'article_locale_unique', columns: ['article_id', 'locale'])]
class ArticleTranslation implements TranslatingInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 10)]
    private string $locale;

    #[ORM\ManyToOne(targetEntity: Article::class, inversedBy: 'translations')]
    #[ORM\JoinColumn(name: 'article_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private ?Article $translatable = null;

    #[ORM\Column(type: 'string', length: 255)]
    private string $title;

    #[ORM\Column(type: 'text', nullable: true)]
    private ?string $content = null;

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

    public function setLocale(string $locale): void
    {
        $this->locale = $locale;
    }

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

    public function setTranslatable(Article $translatable): void
    {
        $this->translatable = $translatable;
    }

    public function getTranslatable(): ?Article
    {
        return $this->translatable;
    }

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

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

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

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



namespace App\Controller;

use App\Entity\Article;
use App\Repository\ArticleRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ArticleController extends AbstractController
{
    #[Route('/article/{id}', name: 'article_show')]
    public function show(Article $article): Response
    {
        // The bundle automatically sets the locale from the request
        // Access translated properties through magic methods
        $title = $article->title;        // or $article->getTitle()
        $content = $article->content;    // or $article->getContent()

        return $this->render('article/show.html.twig', [
            'article' => $article,
            'title' => $title,
            'content' => $content,
        ]);
    }
}



namespace App\Repository;

use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;

class ArticleRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Article::class);
    }

    /**
     * Get all articles with translations joined
     */
    public function findAllWithTranslations(): array
    {
        return $this->createQueryBuilderWithTranslations()
            ->getQuery()
            ->getResult();
    }

    /**
     * Find one article by ID with translations
     */
    public function findOneWithTranslations(int $id): ?Article
    {
        return $this->createQueryBuilderWithTranslations()
            ->andWhere('a.id = :id')
            ->setParameter('id', $id)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * Create query builder with translations joined
     */
    private function createQueryBuilderWithTranslations(): QueryBuilder
    {
        return $this->createQueryBuilder('a')
            ->leftJoin('a.translations', 'at')
            ->addSelect('at');
    }
}



namespace App\Form;

use App\Entity\ArticleTranslation;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ArticleTranslationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('locale', HiddenType::class)
            ->add('title', TextType::class, [
                'label' => 'Title',
                '



namespace App\Form;

use App\Entity\Article;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('translations', CollectionType::class, [
                'entry_type' => ArticleTranslationType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'label' => 'Translations',
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
        ]);
    }
}



namespace App\Entity;

use Janwebdev\TranslatableEntityBundle\Model\TranslatableWrapper;

class Article extends TranslatableWrapper
{
    // ... your code ...

    /**
     * Return true to use the first available translation as fallback
     * when translation for current locale is not found
     */
    protected function acceptFirstTranslationAsDefault(): bool
    {
        return true; // Default: false
    }

    /**
     * Return false to NOT use default locale translation as fallback
     */
    protected function acceptDefaultLocaleTranslationAsDefault(): bool
    {
        return true; // Default: true
    }

    /**
     * Handle the case when no translation is found
     * Override to provide custom behavior (e.g., return empty translation)
     */
    protected function handleTranslationNotFound(): never
    {
        // Create an empty translation instead of throwing exception
        $class = ArticleTranslation::class;
        $this->translation = new $class();
        $this->translation->setLocale($this->locale->getLocale());

        return $this->translation;
    }
}