Gedmo Doctrine Extensions - Sluggable + Translatable Yaml Configuration

…衆ロ難τιáo~ 提交于 2019-12-04 23:08:02

After a bit of struggling, I think I've managed to find a solution to your problem. Let's get down to business.

First of all, I've noticed you're using StofDoctrineExtensionsBundle which is a wrapper of the Gedmo Doctrine2 extensions.

It might have seemed easier to install/use but in the long run it complicates matters. In this case, for example, in order to solve your problem you have to modify listeners and with that bundle the only solution I can come up with is to hack the code and change the priority of the service manually. This leads to the first solution:

First solution: hack the bundle (not a good one)

The services can be found in

StofDoctrineExtensionsBundle/Resources/config/listeners.xml

You need to find and modify the sluggable service and increase its priority. That way, this service will execute before the translatable service. The slug would be created first and translatable would store it nicely.

Modification (haven't tried it though I think it might work):

<service id="stof_doctrine_extensions.listener.sluggable" class="%stof_doctrine_extensions.listener.sluggable.class%" public="false">
    <tag name="kernel.cache_warmer" priority="1" />
    <call method="setAnnotationReader">
        <argument type="service" id="annotation_reader" />
    </call>
</service>

However, I don't like this solution. To be honest, I don't like wrappers. I'm going to give you another solution that I find more satisfactory (and I tried it and works).

Second solution (and the best one)

First off, get rid of StofDoctrineExtensionsBundle. Secondly, install Gedmo Doctrine2 extensions (how to do it here).

You'll notice that in order to install the extensions you had to create a file in your bundle containing the necessary services. That is good. Go to that file and modify the priority for the sluggable service:

gedmo.listener.sluggable:
        class: Gedmo\Sluggable\SluggableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default, priority: 1 }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]

And now it is ready.

These are my yml and php for the entity Post (I think they are really similar to yours but can differ):

YML

DSG\AcmeBundle\Entity\Post:
    type:  entity
    table: posts
    gedmo:
        soft_deleteable:
            field_name: deleted_at
        translation:
            locale: locale
    fields:
        id:
            type: integer
            length: 11
            id: true
            generator:
                strategy: AUTO
        title:
            type: string
            length: 255
            gedmo:
                - translatable
        slug:
            type: string
            length: 255
            unique: true
            gedmo:
                translatable: {}
                slug:
                    separator: _
                    style: camel
                    fields:
                        - title

And the PHP

namespace DSG\AcmeBundle\Entity;

use Gedmo\Translatable\Translatable;

class Post implements Translatable
{
    /**
     * @var integer
     */
    private $id;

    /**
     * @var string
     */
    private $title;

    /**
     * @var string
     */
    private $slug;

    /**
     * @var string
     */
    private $locale;

    public function setTranslatableLocale($locale)
    {
        $this->locale = $locale;
    }


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

    /**
     * Set title
     *
     * @param string $title
     * @return Post
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

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

    /**
     * Set slug
     *
     * @param string $slug
     * @return Post
     */
    public function setSlug($slug)
    {
        $this->slug = $slug;

        return $this;
    }

    /**
     * Get slug
     *
     * @return string
     */
    public function getSlug()
    {
        return $this->slug;
    }
}

When you create and entity and store it in the database, it will create the slug and store the title and the slug in the translation table (if you change the locale).

If you try:

$em = $this->getDoctrine()->getEntityManager();
$article = new Post();
$article->setTitle('the title');
$em->persist($article);
$em->flush();

$article = $this->getDoctrine()->getRepository('DSGMDayBundle:Post')->find(1);
$article->setTitle('my title in de');
$article->setTranslatableLocale('de_de'); // change locale
$em->persist($article);
$em->flush();

You'll see that your translation table gets two rows, one for the title and the other for the slug for the local de_de.

Hope it helps and kind regards.

Something weird to me : Your ORM config by entity is done in 2 files (yaml and annotation) ?

Listeners look to be well loaded, in the proper order. Maybe it's the entity field config in orm.yml. Try to invert translatable and sluggable on the field slug:

CS\ContentBundle\Entity\Post:
  type:  entity
  table: posts
  repositoryClass: CS\ContentBundle\Entity\PostRepository
  gedmo:
    soft_deleteable:
      field_name: deleted_at
    translation:
      locale: locale
  fields:
    id:
      type: integer
      length: 11
      id: true
      generator:
        strategy: AUTO
    title:
      type: string
      length: 500
      gedmo:
        - translatable
    slug:
      type: string
      length: 500
      gedmo:
        slug:
          separator: -
          fields:
            - title
        translatable: {}

Your configurations is look like correct. However, did you try to update vendors? Maybe some files may be corrupted.

If update process does not work, could you full compare configurations with mine:

doctrine:
    dbal:
        driver:   "%database_driver%"
        host:     "%database_host%"
        port:     "%database_port%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
        charset:  UTF8
    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        auto_mapping: true
        dql:
            string_functions:
                GroupConcat: DoctrineExtensions\Query\Mysql\GroupConcat
                IF: DoctrineExtensions\Query\Mysql\IfElse
        filters:
            softdeleteable:
                class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
                enabled: true
        mappings:
            StofDoctrineExtensionsBundle: ~
            gedmo_tree:
                type: annotation
                prefix: Gedmo\Tree\Entity
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"
                alias: GedmoTree
                is_bundle: false
            translatable:
                type: annotation
                alias: Gedmo
                prefix: Gedmo\Translatable\Entity
                # make sure vendor library location is correct
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"

And make sure that you do not override the gedmo.listener.translatable on your services.yml. If there is any code that overrides the default one, remove it. Default configuration:

gedmo.listener.translatable:
        class: Gedmo\Translatable\TranslatableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]
            - [ setDefaultLocale, [ %locale% ] ]
            - [ setTranslationFallback, [ false ] ]

I use StofDoctrineExtensionsBundle for sluggable and translatable and had the same problem.

I have searched and tried a while and found a solution working for me:


Entity: the slug field gets generated from the name field

use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\Translatable\Translatable;

/**
 * @var string
 *
 * @Gedmo\Translatable
 * @ORM\Column(name="name", type="string", length=150, unique=true)
 */
private $name;

/**
 * @Gedmo\Translatable
 * @Gedmo\Slug(fields={"name"}, updatable=true)
 * @ORM\Column(type="string", unique=true)
 */
private $slug;

config.yml: here you have to set persist_default_translation: true https://github.com/Atlantic18/DoctrineExtensions/issues/542#issuecomment-12983553

parameters:
    locale: en

doctrine:
    orm:
        auto_generate_proxy_classes: '%kernel.debug%'
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            gedmo_translatable:
                type: annotation
                prefix: Gedmo\Translatable\Entity
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"
                alias: GedmoTranslatable # (optional) it will default to the name set for the mapping
                is_bundle: false
            gedmo_translator:
                type: annotation
                prefix: Gedmo\Translator\Entity
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translator/Entity"
                alias: GedmoTranslator # (optional) it will default to the name set for the mapping
                is_bundle: false

stof_doctrine_extensions:
    default_locale: '%locale%'
    translation_fallback: true
    persist_default_translation: true    # I think this does the trick
    orm:
        default:
            sluggable: true
            translatable: true

DefaultController use ParamConverter for calling a query which returns correct entity for current locale

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

/**
 * @Route("/entry/{slug}", name="entry_detail")
 * @ParamConverter("entry", class="AppBundle:Entry", options={"repository_method" = "findOneByCriteria"})
 */
public function entryDetailAction(Request $request, Entry $entry)
{
    return $this->render('frontend/entry.html.twig', [
        'entry' => $entry,
    ]);
}

Entity Repository with the query method to return entity

/**
 * Find an entry by criteria
 * Need this special function, because of translatable
 * https://github.com/stof/StofDoctrineExtensionsBundle/issues/232
 *
 * @param $params
 * @return mixed
 */
public function findOneByCriteria(array $params)
{
    $query = $this->createQueryBuilder('e');
    $i = 0;
    foreach ($params as $column => $value) {
        if ($i < 1) {
            $query->where("e.$column = :$column");
        } else {
            $query->andWhere("e.$column = :$column");
        }
        $query->setParameter($column, $value);
        $i++;
    }
    $query = $query->getQuery();
    $query->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker');
    return $query->getOneOrNullResult();
}

I hope this example helps someone.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!