Skip to content

[6.x] Fix $isNew detection after Asset::move()#14140

Merged
jasonvarga merged 1 commit intostatamic:6.xfrom
lwekuiper:fix/asset-move-isNew-detection
Mar 5, 2026
Merged

[6.x] Fix $isNew detection after Asset::move()#14140
jasonvarga merged 1 commit intostatamic:6.xfrom
lwekuiper:fix/asset-move-isNew-detection

Conversation

@lwekuiper
Copy link
Copy Markdown
Contributor

@lwekuiper lwekuiper commented Mar 3, 2026

Problem

When Asset::move() is called (e.g. by a listener on AssetUploaded), the subsequent save() incorrectly evaluates $isNew = true with the Eloquent driver. This causes AssetCreating/AssetCreated events to fire for what is actually a move — a semantic bug that can abort the save entirely if any AssetCreating listener returns false.

Example: An AssetUploaded listener that organizes uploads into date-based folders:

class OrganizeArticleAssets
{
    public function handle(AssetUploaded $event): void
    {
        $asset = $event->asset;
        $asset->move(now()->format('Y/m/d'));
    }
}

This triggers AssetCreating on the internal save(), which can abort the move if any other listener returns false — even though the asset already exists and is simply being relocated.

Root cause: The $isNew check creates a fresh lookup at the current (post-move) path:

$isNew = is_null($this->container()->asset($this->path()));

With the Eloquent driver, exists() checks the DB which still has the old path, returning false and making $isNew = true.

Solution

Use getOriginal('path') to detect whether the path has changed since the last syncOriginal() call. If it has, the asset was moved — not created.

$pathHasChanged = $this->getOriginal('path') !== $this->path();
$isNew = $pathHasChanged ? false : is_null($this->container()->asset($this->path()));

This works because syncOriginal() is called after every save(), storing the path at that point. After move() changes the path but before the next save(), getOriginal('path') returns the pre-move path — signaling a move.

Test

Added a test that verifies after move():

  • AssetSaving/AssetSaved are dispatched
  • AssetCreating/AssetCreated are not dispatched

When Asset::move() changes the path, the subsequent save() incorrectly
detected the asset as new because the container lookup at the new path
found nothing. This caused AssetCreating/AssetCreated events to fire
for what is actually a move, which could abort the save if any
AssetCreating listener returns false.

Use getOriginal('path') to detect path changes — if the path changed,
it's a move, not a creation.
@jasonvarga jasonvarga merged commit 74794f5 into statamic:6.x Mar 5, 2026
20 checks passed
@lwekuiper lwekuiper deleted the fix/asset-move-isNew-detection branch March 12, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants