1

I'm calling EVENT_BEFORE_SAVE_ELEMENT to trigger a task that then updates the entry, but it's causing a loop?

The event is triggered when a specific entry type is saved:

Event::on(
  Elements::class,
  Elements::EVENT_BEFORE_SAVE_ELEMENT,
  function(Event $event) {
    $entry = $event->sender;
    if ($event->element instanceof \craft\elements\Entry) {
      $entry = $event->element;
      if (ElementHelper::isDraftOrRevision($entry)) {
        return;
      }
      if(($entry->sectionId == 18) && ($entry->typeId == 26)) {
        $signpostURL = $entry->getFieldValue('signpostUrl');
        if(($signpostURL) && ($signpostURL !== '')) {
          Craft::$app->queue->push(new BrowshotTask([
            'description' => 'Grabbing screenshot for '.$signpostURL,
            'url' => $signpostURL,
            'entryId' => $entry->id
          ]));
        }
      }
    }
  }
);

BrowshotTask then performs some API calls to a third-party service, gets a screenshot, saves an asset and saves the asset in the asset field of the entry.

But this triggers the event again and causes a loop:

$entry->setFieldValue('browsershotUrl', [$assetId]);
if(Craft::$app->elements->saveElement($entry)) {

Sometimes BrowshotTask can take 20 seconds due to the third-party service.. so I thought putting it in a task would be a great idea, but now it's causing me so many problems :(

2

The easiest solution for this would be either a special fieldvalue or a property in a behavior that you set in your job.

class MyJob extends BaseJob
{
    public function execute($queue): bool
    {
        // do some api calls here
        // ....
        $entry->isSavedViaApi = true;
        $entry->setFieldValue('browsershotUrl', [$assetId]);
        Craft::$app->elements->saveElement($entry)
        // ....
    }
}

and in your event, you can check against this property

Event::on(
  Elements::class,
  Elements::EVENT_BEFORE_SAVE_ELEMENT,
  function(Event $event) {
    $entry = $event->sender;
    if ($event->element instanceof \craft\elements\Entry) {
      $entry = $event->element;
      if (ElementHelper::isDraftOrRevision($entry)) {
        return;
      }

      if(($entry->sectionId == 18) && ($entry->typeId == 26)) {

        // make sure to check the section first, because otherwise the entry
        // may not have this property of field
        if($entry->isSavedViaApi === true){
            return;
        }
        //


        $signpostURL = $entry->getFieldValue('signpostUrl');
        if(($signpostURL) && ($signpostURL !== '')) {
          Craft::$app->queue->push(new BrowshotTask([
            'description' => 'Grabbing screenshot for '.$signpostURL,
            'url' => $signpostURL,
            'entryId' => $entry->id
          ]));
        }
      }
    }
  }
);

If you want to use a behavior instead of a field value for this

use yii\base\Behavior;

class MyBehavior extends Behavior
{
    public $isSavedViaApi = false;
}

and in your main module file

use craft\elements\Entry;
use craft\events\DefineBehaviorsEvent;

Event::on(
    Entry::class,
    Entry::EVENT_DEFINE_BEHAVIORS,
    static function(DefineBehaviorsEvent $event) {
        //so every element gets a isSubscribed attached
        $event->behaviors[] = MyBehavior::class;
    }
);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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