1

My entries' titles are auto-generated from several fields values on first save.

When one of these fields values change, I would like to update the auto-generated slug also:

Event::on(
    \craft\services\Elements::class,
    \craft\services\Elements::EVENT_AFTER_SAVE_ELEMENT,
    function (Event $event) {
        $entry = Entry::find()
            ->id($event->element->id)
            ->one();

        if ([] !== $entry) {
            $title = craft\helpers\StringHelper::toAscii($entry->id.'-'.$entry->title);
            $title = craft\helpers\StringHelper::toLowerCase($title);
            $title = str_replace(' ', '-', $title);

            // 👷 I tried this below, but none works
            $entry->slug = $title; // ⬅️ Not working
            $entry->setFieldValue('slug', $title); // ⬅️ Error
            Craft::$app->elements->saveElement($entry); // ⬅️ Infinite loop
            $event->element->slug = $event->element->id; // ⬅️ Not working
        }
    }
);

Unfortunately, none of these methods works. Any idea how to achieve this?

3 Answers 3

4

You can change the value in the DB directly instead of using the Elements service

$entry = $event->element;
$elementId = $entry->id;
$siteId = $entry->siteId;
Craft::$app->getDb()->createCommand()
    ->update(
        '{{%elements_sites}}',
        ['slug' => ElementHelper::createSlug('new-slug')],
        [
            'elementId' => $elementId,
            'siteId'    => $siteId
        ]
    )->execute();

By the way the following condition [] !== $entry won't be any good when you fetch an element with one() because one returns an object of instance ElementInterface or null but never an empty array. You can check !== null instead and if you fetch things via all you can perform an empty check if( empty($entries) === false)

1
  • Awesome, thank you! I had never thought of the DB way. Thank you also for the other explanations and advice, I really appreciate.
    – Romain P.
    Commented Feb 13, 2019 at 13:28
2

Another shorter and probably slightly more performant solution, using EVENT_BEFORE_SAVE:

/*
* Automatically update Element slugs to match the title if updated.
*/
Event::on(
    Element::class,
    Element::EVENT_BEFORE_SAVE,
    function(craft\events\ModelEvent $event) {
        $entry = $event->sender;
        $entry->slug = ElementHelper::createSlug($entry->title);
    },
    false
);
5
  • This solution is not working, you should review the suggested code.
    – Romain P.
    Commented Apr 29, 2019 at 15:53
  • Works fine for me (it's tested and being used). Did you actually try it?
    – Udo
    Commented Apr 29, 2019 at 16:57
  • @RomainPoirier it will not work with EVENT_AFTER_SAVE_ELEMENT but it does with EVENT_BEFORE_SAVE_ELEMENT
    – Udo
    Commented Apr 29, 2019 at 17:03
  • Yep, I tried but but too quickly, I'm sorry. I was missing the loading of craft\base\Element and craft\helpers\ElementHelper. Would you use EVENT_BEFORE_SAVE_ELEMENT instead of EVENT_BEFORE_SAVE? I can't find official reference of EVENT_BEFORE_SAVE in the Craft 3 docs. If you know, can you explain the utility of false at the end? Thank you!
    – Romain P.
    Commented Apr 30, 2019 at 8:10
  • Since recent Craft updates, $entry->slug = ElementHelper::createSlug($entry->title); doesn't works as expected: the slug is sometimes saved as empty. Delete $entry->slug = and only keep ElementHelper::createSlug($entry->title); seems to fix it.
    – Romain P.
    Commented Sep 23, 2019 at 9:23
1

I finally use this simple solution, which works pretty fine:

Event::on(
    Element::class,
    Element::EVENT_BEFORE_SAVE,
    function(craft\events\ModelEvent $event) {
        $entry = $event->sender;
        if (!\craft\helpers\ElementHelper::isDraftOrRevision($entry)) {
            if (!empty($entry->id)) {
                $entry->slug = $entry->title;
            }
        }
    },
    false
);

Where $entry->slug = $entry->title syncs the slug to the title if the entry is not new (!empty($entry->id)).

UPDATE:

$entry->slug = ($entry->title !== null) ? \craft\helpers\StringHelper::slugify($entry->title) : $entry->slug;

This prevents to get a wrong slug on first save.

1
  • Due to the draft changes, this is the right approach for Craft 3.2 and later. Commented Oct 2, 2019 at 8:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.