haxpanel
haxpanel

Reputation: 4678

How to implement a nice solution for multilang entity slug based routes in Symfony2

I'd like to create a simple bundle to handle some multilingual pages in a website with translated slugs.

Based on translatable, sluggable and i18nrouting

  1. implemented an entity (Page) with title, content, slug fields + locale property as the doc says
  2. created a new Page set its title and content then translated it by $page->setTranslatableLocale('de'); and set those fields again with the german values, so that the data in the tables looks fine, they are all there
  3. implemented the controller with type hinting signature: public function showAction(Page $page)
  4. generated some urls in the template by: {{ path("page_show", {"slug": "test", "_locale": "en"}) }} and {{ path("page_show", {"slug": "test-de", "_locale": "de"}) }}, routes are generated fine, they look correct (/en/test and /de/test-de)
  5. clicking on them:

Only the "en" translation works, the "de" one fails:

MyBundle\Entity\Page object not found.

How to tell Symfony or the Doctrine or whatever bundle to use the current locale when retrieving the Page? Do I have to create a ParamConverter then put a custom DQL into it the do the job manually? Thanks!

Upvotes: 1

Views: 1290

Answers (3)

Skriptas
Skriptas

Reputation: 889

TranslationWalker nicely gets the entity in active locale:

class PagesRepository extends \Doctrine\ORM\EntityRepository
{
    public function findTranslatedBySlug(string $slug)
    {
        $queryBuilder = $this->createQueryBuilder("p");

        $queryBuilder
            ->where("p.slug = :slug")
            ->setParameter('slug', $slug)
        ;

        $query = $queryBuilder->getQuery();

        $query->setHint(
            Query::HINT_CUSTOM_OUTPUT_WALKER,
            'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
        );

        return $query->getSingleResult();
    }
}

And in controller

 /**
 * @Entity("page", expr="repository.findTranslatedBySlug(slug)")
 * @param         $page
 *
 * @return Response
 */
public function slug(Pages $page)
{
// thanks to @Entity annotation (Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity) 
// Pages entity is automatically retrieved by slug

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

Upvotes: 1

haxpanel
haxpanel

Reputation: 4678

Just found another solution which I think is much nicer and i'm going to use that one!

Implemented a repository method and use that in the controller's annotation:

@ParamConverter("page", class="MyBundle:Page", options={"repository_method" = "findTranslatedOneBy"})

public function findTranslatedOneBy(array $criteria, array $orderBy = null)
{
    $page = $this->findOneBy($criteria, $orderBy);

    if (!is_null($page)) {
        return $page;
    }

    $qb = $this->getEntityManager()
        ->getRepository('Gedmo\Translatable\Entity\Translation')
        ->createQueryBuilder('t');

    $i = 0;
    foreach ($criteria as $name => $value) {
        $qb->orWhere('t.field = :n'. $i .' AND t.content = :v'. $i);
        $qb->setParameter('n'. $i, $name);
        $qb->setParameter('v'. $i, $value);
        $i++;
    }

    /** @var \Gedmo\Translatable\Entity\Translation[] $trs */
    $trs = $qb->groupBy('t.locale', 't.foreignKey')->getQuery()->getResult();

    return count($trs) == count($criteria) ? $this->find($trs[0]->getForeignKey()) : null;
}

It has one disadvantage there is no protection against same translated values ...

Upvotes: 1

haxpanel
haxpanel

Reputation: 4678

I found out a solution which i'm not sure the best, but works.

Implemented a PageParamConverter:

class PageParamConverter extends DoctrineParamConverter
{
const PAGE_CLASS = 'MyBundle:Page';

public function apply(Request $request, ParamConverter $configuration)
{
    try {
        return parent::apply($request, $configuration);
    } catch (NotFoundHttpException $e) {
        $slug = $request->get('slug');
        $name = $configuration->getName();
        $class = $configuration->getClass();
        $em = $this->registry->getManagerForClass($class);

        /** @var \Gedmo\Translatable\Entity\Translation $tr */
        $tr = $em->getRepository('Gedmo\Translatable\Entity\Translation')
            ->findOneBy(['content' => $slug, 'field' => 'slug']);

        if (is_null($tr)) {
            throw new NotFoundHttpException(sprintf('%s object not found.', $class));
        }

        $page = $em->find($class, $tr->getForeignKey());

        $request->attributes->set($name, $page);
    }

    return true;
}

public function supports(ParamConverter $configuration)
{
    $name = $configuration->getName();
    $class = $configuration->getClass();

    return parent::supports($configuration) && $class == self::PAGE_CLASS;
}
}

Upvotes: 0

Related Questions