spatie / laravel-medialibrary

Associate files with Eloquent models
https://spatie.be/docs/laravel-medialibrary
MIT License
5.78k stars 1.07k forks source link

Update to 11.10 from 11.9.2 breaking with error "There is no conversion named `thumb`" #3724

Open SlyDave opened 1 day ago

SlyDave commented 1 day ago

On 11.9.2 everything works.

On 11.10, the following error occurs when updating or saving media:

{
    "message": "There is no conversion named `thumb`",
    "exception": "Spatie\\MediaLibrary\\MediaCollections\\Exceptions\\InvalidConversion",
    "file": "C:\\www\\workrest\\vendor\\spatie\\laravel-medialibrary\\src\\MediaCollections\\Exceptions\\InvalidConversion.php",
    "line": 11,
    "trace": [
        {
            "file": "C:\\www\\workrest\\vendor\\spatie\\laravel-medialibrary\\src\\Conversions\\ConversionCollection.php",
            "line": 46,
            "function": "unknownName",
            "class": "Spatie\\MediaLibrary\\MediaCollections\\Exceptions\\InvalidConversion",
            "type": "::"
        },
        {
            "file": "C:\\www\\workrest\\app\\Services\\MediaLibrary\\Filesystem.php",
            "line": 21,
            "function": "getByName",
            "class": "Spatie\\MediaLibrary\\Conversions\\ConversionCollection",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\spatie\\laravel-medialibrary\\src\\MediaCollections\\Filesystem.php",
            "line": 259,
            "function": "renameConversionFiles",
            "class": "App\\Services\\MediaLibrary\\Filesystem",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\spatie\\laravel-medialibrary\\src\\MediaCollections\\Models\\Observers\\MediaObserver.php",
            "line": 30,
            "function": "syncFileNames",
            "class": "Spatie\\MediaLibrary\\MediaCollections\\Filesystem",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Events\\Dispatcher.php",
            "line": 478,
            "function": "updating",
            "class": "Spatie\\MediaLibrary\\MediaCollections\\Models\\Observers\\MediaObserver",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Events\\Dispatcher.php",
            "line": 286,
            "function": "Illuminate\\Events\\{closure}",
            "class": "Illuminate\\Events\\Dispatcher",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Events\\Dispatcher.php",
            "line": 266,
            "function": "invokeListeners",
            "class": "Illuminate\\Events\\Dispatcher",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Events\\Dispatcher.php",
            "line": 232,
            "function": "dispatch",
            "class": "Illuminate\\Events\\Dispatcher",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Concerns\\HasEvents.php",
            "line": 215,
            "function": "until",
            "class": "Illuminate\\Events\\Dispatcher",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Model.php",
            "line": 1221,
            "function": "fireModelEvent",
            "class": "Illuminate\\Database\\Eloquent\\Model",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Model.php",
            "line": 1155,
            "function": "performUpdate",
            "class": "Illuminate\\Database\\Eloquent\\Model",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\app\\Models\\User.php",
            "line": 390,
            "function": "save",
            "class": "Illuminate\\Database\\Eloquent\\Model",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\app\\LegacyAPI\\Controllers\\UsersController.php",
            "line": 86,
            "function": "updateSignature",
            "class": "App\\Models\\User",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Concerns\\ManagesTransactions.php",
            "line": 32,
            "function": "App\\LegacyAPI\\Controllers\\{closure}",
            "class": "App\\LegacyAPI\\Controllers\\UsersController",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\DatabaseManager.php",
            "line": 471,
            "function": "transaction",
            "class": "Illuminate\\Database\\Connection",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Support\\Facades\\Facade.php",
            "line": 358,
            "function": "__call",
            "class": "Illuminate\\Database\\DatabaseManager",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\app\\LegacyAPI\\Controllers\\UsersController.php",
            "line": 79,
            "function": "__callStatic",
            "class": "Illuminate\\Support\\Facades\\Facade",
            "type": "::"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Controller.php",
            "line": 54,
            "function": "update",
            "class": "App\\LegacyAPI\\Controllers\\UsersController",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
            "line": 43,
            "function": "callAction",
            "class": "Illuminate\\Routing\\Controller",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
            "line": 264,
            "function": "dispatch",
            "class": "Illuminate\\Routing\\ControllerDispatcher",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
            "line": 210,
            "function": "runController",
            "class": "Illuminate\\Routing\\Route",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
            "line": 808,
            "function": "run",
            "class": "Illuminate\\Routing\\Route",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 144,
            "function": "Illuminate\\Routing\\{closure}",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\app\\Http\\Middleware\\JsonResponseMiddleware.php",
            "line": 15,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "App\\Http\\Middleware\\JsonResponseMiddleware",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\SubstituteBindings.php",
            "line": 51,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Routing\\Middleware\\SubstituteBindings",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\ThrottleRequests.php",
            "line": 161,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\ThrottleRequests.php",
            "line": 127,
            "function": "handleRequest",
            "class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\ThrottleRequests.php",
            "line": 89,
            "function": "handleRequestUsingNamedLimiter",
            "class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Routing\\Middleware\\ThrottleRequests",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Auth\\Middleware\\Authenticate.php",
            "line": 64,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Auth\\Middleware\\Authenticate",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 119,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
            "line": 807,
            "function": "then",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
            "line": 786,
            "function": "runRouteWithinStack",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
            "line": 750,
            "function": "runRoute",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
            "line": 739,
            "function": "dispatchToRoute",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
            "line": 201,
            "function": "dispatch",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 144,
            "function": "Illuminate\\Foundation\\Http\\{closure}",
            "class": "Illuminate\\Foundation\\Http\\Kernel",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\nova\\src\\Http\\Middleware\\ServeNova.php",
            "line": 23,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Laravel\\Nova\\Http\\Middleware\\ServeNova",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
            "line": 21,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull.php",
            "line": 31,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
            "line": 21,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TrimStrings.php",
            "line": 51,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\TrimStrings",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Middleware\\ValidatePostSize.php",
            "line": 27,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Http\\Middleware\\ValidatePostSize",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance.php",
            "line": 110,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Middleware\\HandleCors.php",
            "line": 62,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Http\\Middleware\\HandleCors",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Middleware\\TrustProxies.php",
            "line": 58,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Http\\Middleware\\TrustProxies",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks.php",
            "line": 22,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
            "line": 119,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
            "line": 176,
            "function": "then",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
            "line": 145,
            "function": "sendRequestThroughRouter",
            "class": "Illuminate\\Foundation\\Http\\Kernel",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Application.php",
            "line": 1190,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Kernel",
            "type": "->"
        },
        {
            "file": "C:\\www\\workrest\\public\\index.php",
            "line": 17,
            "function": "handleRequest",
            "class": "Illuminate\\Foundation\\Application",
            "type": "->"
        },
        {
            "file": "C:\\Program Files\\Herd\\resources\\app.asar.unpacked\\resources\\valet\\server.php",
            "line": 139,
            "function": "require"
        }
    ]
}
    public function registerMediaCollections(): void
    {
        $this->addMediaCollection('avatar')
            ->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png', 'image/webp'])
            ->singleFile()
            ->useDisk(config('filesystems.avatars'));

        $this->addMediaCollection('signatures')
            ->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png', 'image/webp'])
            ->useDisk(config('filesystems.signatures'));
    }

    public function registerMediaConversions(?MediaLibraryMedia $media = null): void
    {
        $this->addMediaConversion('thumb')
            ->width(100)
            ->height(100)
            ->performOnCollections('avatar')
            ->nonQueued();

        $this->addMediaConversion('large')
            ->width(400)
            ->height(400)
            ->performOnCollections('avatar')
            ->nonQueued();
    }

Note I am updating signatures, not avatar, and yet there is an error for "thumb", which isn't defined as a conversion on the signatures collection, so I'm not sure why it's trying to look it up?!

Other things that might be important

    /*
     * Moves media on updating to keep path consistent. Enable it only with a custom
     * PathGenerator that uses, for example, the media UUID.
     */
    'moves_media_on_update' => true,
    /*
     * The class that contains the strategy for determining a media file's path.
     */
    //'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
    'path_generator' => \App\Services\MediaLibrary\UuidPathGenerator::class,
namespace App\Services\MediaLibrary;

use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator;

class UuidPathGenerator extends DefaultPathGenerator
{
    /**
     * Get a unique base path for the given media.
     */
    protected function getBasePath(Media $media): string
    {
        $prefix = config('media-library.prefix', '');

        if ($prefix !== '') {
            return $prefix.'/'.$media->getAttribute('uuid');
        }

        return $media->getAttribute('uuid');
    }
}

media-library.prefix = '' so the above just returns uuid for the path, giving paths like "signature_url": "/storage/cf244a39-fa52-42e5-a5e0-62a9ffa5564b/33275f9e-b3a4-44a6-9334-2384f9b6eeaf.jpg"

More information, the media being added is added from base64 so it has a .tmp extension, that is then changed to the correct extension detected by the mime-type (something that media-lib doesn't do automatically! - see the graveyard that is - https://github.com/spatie/laravel-medialibrary/discussions/3089), this causes the Observer to trigger and run, which is when the problems occurs, within syncFileNames() inside Spatie\MediaLibrary\MediaCollections\Models\Observers\MediaObserver.

Something is causing backwards breaking in 11.10

SlyDave commented 1 day ago

The following pull added to 11.10 causes the issue: https://github.com/spatie/laravel-medialibrary/pull/3715

Reverting:

        $conversion = $this
            ->getConversions($this->media->collection_name)
            ->first(fn (Conversion $conversion) => $conversion->getName() === $name);

back to

        $conversion = $this->first(fn (Conversion $conversion) => $conversion->getName() === $name);

fixes the crash

srustamov commented 23 hours ago

same issue

SlyDave commented 23 hours ago

ok, so the new code does what is expected and returns null for conversion when, looking for conversions on a collection that doesn't have conversions.

The problem is this is what is throwing the exception

        if (! $conversion) {
            throw InvalidConversion::unknownName($name);
        }

The question then becomes why is ConversionCollection::getByName() being called for a collection that doesn't have conversions

which brings you toFilesystem->renameConversionFiles() -> foreach ($media->getMediaConversionNames()... which comes from Filesystem->syncFileNames which comes from the aforementioned MediaObserver->updating()

If we follow that, we can see that all the code calls $media->getMediaConversionNames() and loops over them, it doesn't filter this list down by the conversions actually available for the currently observed collection - so it's running renameConverionsFiles on all conversions configured for the media, not just the ones for the collection - and thus when it hits the getByName which now returns null, it crashes.

// $media->getMediaConversionNames() returns all media conversions (regardless of collection)
// $conversionCollection only contains conversions for the collection the media is for
// thus a call to getByName() on each of the media is going to return null if a media has multiple collections but not all collections have all conversions defined

        $conversionCollection = ConversionCollection::createForMedia($media);

        foreach ($media->getMediaConversionNames() as $conversionName) {
            $conversion = $conversionCollection->getByName($conversionName); <--- crash

I think the woopies here is thinking that $conversionCollection means conversions for a Media Collection, and not a collection of Conversions for the Media :D