Jonathan Scheiber

Mettre en place CKEditor 5 avec Symfony, Asset Mapper et Stimulus

Temps de lecture : 3 minutes 5 commentaires
Mettre en place CKEditor 5 avec Symfony, Asset Mapper et Stimulus

La mise en place de CKEditor 5 dans Symfony, que ce soit avec Asset Mapper ou Webpack Encore, n'est pas chose aisée.

Dans cet article, nous allons voir comment faire cela avec Symfony, Asset Mapper et un petit contrôleur Stimulus chargé en lazy, afin de ne pas charger CKEditor sur les pages n'en ayant pas besoin.

Les prérequis de cet article :

Ajout des ressources CKEditor

Installons d'abord les fichiers JS/CSS de CKEditor avec Asset Mapper :

bin/console importmap:require ckeditor5
bin/console importmap:require ckeditor5/dist/ckeditor5.min.css
# Si vous avez besoin de traductions, par exemple ici les traductions en français
bin/console importmap:require ckeditor5/translations/fr.js

Configuration de CKEditor

Passons au fichier JS qui va nous permettre de configurer CKEditor : clé de licence, traductions et plugins chargés...

Dans cet exemple, on importe des plugins de base et on configure la barre d'outils associée, tout en chargeant les traductions en français.

// assets/ckeditor5.js
import {
    Autoformat,
    Bold,
    ClassicEditor,
    Essentials,
    Italic,
    Link,
    Paragraph,
} from 'ckeditor5';
// Si vous devez importer des traductions, ici les traductions en français
import coreTranslations from 'ckeditor5/translations/fr.js';
import 'ckeditor5/dist/ckeditor5.min.css';

export default class EnhancedEditor extends ClassicEditor {}

EnhancedEditor.builtinPlugins = [Autoformat, Bold, Essentials, Italic, Link, Paragraph];

EnhancedEditor.defaultConfig = {
    licenseKey: 'GPL',
    toolbar: [
        'bold',
        'italic',
        '|',
        'link',
        '|',
        'undo',
        'redo',
    ],
    // Vous pouvez supprimer la ligne suivante si vous n'avez pas besoin de charger des traductions
    translations: [coreTranslations],
};

Contrôleur Stimulus

Voici maintenant le contrôleur Stimulus, qui va permettre de charger les ressources à la demande (le contrôleur étant chargé de manière lazy), et d'instancier les éditeurs s'ils apparaissent dynamiquement sur la page.

// assets/controllers/ckeditor5_controller.js
import { Controller } from '@hotwired/stimulus';
import EnhancedEditor from '../ckeditor5.js';

/* stimulusFetch: 'lazy' */
export default class extends Controller {
    connect() {
        this.editor = EnhancedEditor.create(this.element)
            .then(editor => (this.editor = editor))
            .catch(error => console.error(error));
    }

    disconnect() {
        this.editor.destroy().catch(error => console.error(error));
    }
}

Form type dédié

Pour se faciliter la vie, on va créer un form type dédié qui va permettre d'aisément charger CKEditor dans un formulaire.

<?php

declare(strict_types=1);

namespace App\Form\Type;

use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;

/**
 * @extends AbstractType<string>
 */
final class CKEditor5Type extends AbstractType
{
    public function __construct(
        #[Autowire(service: 'stimulus.helper')]
        private readonly StimulusHelper $stimulusHelper,
    ) {}

    public function finishView(FormView $view, FormInterface $form, array $options): void
    {
        parent::finishView($view, $form, $options);

        $attr = $this->stimulusHelper->createStimulusAttributes();
        $attr->addController('ckeditor5');

        $view->vars['attr'] = $attr->toArray();
    }

    public function getParent(): string
    {
        return TextareaType::class;
    }
}

Enfin, on va pouvoir l'utiliser !

Eh oui, on va enfin pouvoir l'utiliser comme on veut dans nos formulaires ! Et voici comment :

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder->add('content', CKEditor5Type::class);
}

Et voilà, c'est terminé ! :) Si vous avez des questions, remarques, suggestions ... N'hésitez pas à poster un commentaire ci-dessous :)

Commentaires

Posté par Stof le 21/01/2025 à 15:25.
To avoid leaking resources, your Stimulus component should also handle the case where it is remove dynamically from the page, not just when it is added.
For that, you need to store the editor instance (returned asynchronously from `ClassicEditor.create()`) and call its `destroy()` method in `disconnect`
Posté par jmsche le 22/01/2025 à 13:26.
Thank you for your feedback Stof, I fixed the Stimulus controller :)
Posté par Hervé Le Crosnier le 15/03/2025 à 16:57.
Merci beaucoup, c'est très clair, efficace.
Compte-tenu de l'usage massif de CKeditor dans les projets symfony, cela devrait figurer sur leur site...

Avez-vous d'autres éclairage sur l'usage de plugins sur CKeditor avec symfony et les assetmap ?
Posté par jmsche le 15/03/2025 à 18:05.
Bonjour Hervé, de quels éclairages auriez-vous besoin ? Un plugin en particulier ?
Posté par Hervé Le Crosnier le 15/03/2025 à 18:13.
Non, quelque chose de plus général : comment créer un plugin dans cette configuration (je savais le faire en CK4 sans asset mapper). En fait, ce n'est pas tellement ce qu'on met dans le plugin, mais où met-on le plugin.
Et aussi, où met-on les diverses configurations pour les intégrer au niveau du FormType de tel ou tel objet.
En fait, C'est une question sur l'architecture : où met-on les choses et comment les appelle-t-on.
Le genre de chose qui doit être tellement évident pour les spécialistes que c'est justement ce qu'on ne trouve pas... et aussi ce que votre post a très bien réussi : nom des fichiers, positionnement et liens.
Merci encore
Hervé

Ajouter un commentaire