Laravel-medialibrary: Using a custom path generator class fails when a custom Media model is used

Created on 12 Apr 2018  路  10Comments  路  Source: spatie/laravel-medialibrary

Am on version "spatie/laravel-medialibrary": "6.0.0",

I am trying to implement a custom path generator class while using a custom Media class. Both the custom Media class and the Generator class are defined in the medialibrary config file.

But I get an error because in the Spatie\MediaLibrary\PathGenerator\PathGenerator interface, getPath() and getPathForConversions() methods expect Spatie\MediaLibrary\Media but I need to use my a custom Media model which currently extends Spatie\MediaLibrary\Media . Here is the error I am getting :-

Declaration of App\Generators\TenancyMediaPathGenerator::getPath(App\Models\Media $media): string must be compatible with Spatie\MediaLibrary\PathGenerator\PathGenerator::getPath(Spatie\MediaLibrary\Media $media): string

<?php

return [

    /*
     * The filesystems on which to store added files and derived images by default. Choose
     * one or more of the filesystems you configured in app/config/filesystems.php
     */
    'default_filesystem' => 's3',

    /*
     * The maximum file size of an item in bytes. Adding a file
     * that is larger will result in an exception.
     */
    'max_file_size' => 1024 * 1024 * 10,

    /*
     * This queue will be used to generate derived images.
     * Leave empty to use the default queue.
     */
    'queue_name' => '',

    /*
     * The class name of the media model to be used.
     */
    'media_model' => \App\Models\Media::class,

    /*
     * The engine that will perform the image conversions.
     * Should be either `gd` or `imagick`
     */
    'image_driver' => 'gd',

    /*
     * When urls to files get generated this class will be called. Leave empty
     * if your files are stored locally above the site root or on s3.
     */
    'custom_url_generator_class' => /*\App\Generators\TenancyLocalMediaUrlGenerator::class*/ '',

    /*
     * The class that contains the strategy for determining a media file's path.
     */
    'custom_path_generator_class' => \App\Generators\TenancyMediaPathGenerator::class,

    's3' => [
        /*
         * The domain that should be prepended when generating urls.
         */
        'domain' => 'https://xxxxxxx.s3.amazonaws.com',
    ],

    'remote' => [
        /*
         * Any extra headers that should be included when uploading media to
         * a remote disk. Even though supported headers may vary between
         * different drivers, a sensible default has been provided.
         *
         * Supported by S3: CacheControl, Expires, StorageClass,
         * ServerSideEncryption, Metadata, ACL, ContentEncoding
         */
        'extra_headers' => [
            'CacheControl' => 'max-age=604800',
        ],
    ],

    /*
     * These generators will be used to created conversion of media files.
     */
    'image_generators' => [
        Spatie\MediaLibrary\ImageGenerators\FileTypes\Image::class,
        Spatie\MediaLibrary\ImageGenerators\FileTypes\Pdf::class,
        Spatie\MediaLibrary\ImageGenerators\FileTypes\Svg::class,
        Spatie\MediaLibrary\ImageGenerators\FileTypes\Video::class,
    ],

    /*
     * Medialibrary will try to optimize all converted images by
     * removing metadata and applying a little bit of compression. These are
     * the optimizers that will be used by default.
     */
    'image_optimizers' => [
        Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
            '--strip-all',  // this strips out all text information such as comments and EXIF data
            '--all-progressive',  // this will make sure the resulting image is a progressive one
        ],
        Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
            '--force', // required parameter for this package
        ],
        Spatie\ImageOptimizer\Optimizers\Optipng::class => [
            '-i0', // this will result in a non-interlaced, progressive scanned image
            '-o2',  // this set the optimization level to two (multiple IDAT compression trials)
            '-quiet', // required parameter for this package
        ],
        Spatie\ImageOptimizer\Optimizers\Svgo::class => [
            '--disable=cleanupIDs', // disabling because it is known to cause troubles
        ],
        Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
            '-b', // required parameter for this package
            '-O3', // this produces the slowest but best results
        ],
    ],

    /*
     * The path where to store temporary files while performing image conversions.
     * If set to null, storage_path('medialibrary/temp') will be used.
     */
    'temporary_directory_path' => null,

    /*
     * FFMPEG & FFProbe binaries path, only used if you try to generate video
     * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer
     * dependency.
     */
    'ffmpeg_binaries' => '/usr/bin/ffmpeg',
    'ffprobe_binaries' => '/usr/bin/ffprobe',
];

Custom Media class

<?php

namespace App\Models;

use HipsterJazzbo\Landlord\BelongsToTenants;
use Spatie\MediaLibrary\Models\Media as ParentMedia;

class Media extends ParentMedia
{
    use BelongsToTenants;
}

The custom generator class

<?php

namespace App\Generators;

use App\Models\Media;
use Spatie\MediaLibrary\PathGenerator\PathGenerator;

class TenancyMediaPathGenerator implements PathGenerator
{
    protected $path;

    public function __construct()
    {
        $this->path = \App\Helpers\Tenant::currentTenant(request()->uuid);
    }

    public function getPath(Media $media) : string
    {
        return $this->path . $media->id . '/';
    }

    public function getPathForConversions(Media $media) : string
    {
        return $this->getPath($media) . 'conversions/';
    }

}

Is there anything I am missing?

Update: I have edited the post to account for changes in v7.0.0

All 10 comments

does App\Models\Media extends the Media model provided by the package?

Yes it does.

Am on version 6.0.0.

App\Models\Media model extends Spatie\MediaLibrary\Media.php.

here it is:-

<?php

namespace App\Models;

use HipsterJazzbo\Landlord\BelongsToTenants;
use Spatie\MediaLibrary\Media as ParentMedia;

class Media extends ParentMedia
{
    use BelongsToTenants;
}

Seems latest version of media library is v7+, I will try upgrading and see if the problem persists.

Still after upgrading to version 7.0.0 the problem persists.

<?php

namespace App\Models;

use HipsterJazzbo\Landlord\BelongsToTenants;
use Spatie\MediaLibrary\Models\Media as ParentMedia;

class Media extends ParentMedia
{
    use BelongsToTenants;
}

As you can see on the above model, My Media Model extends Spatie\MediaLibrary\Models\Media

PHP doesn't allow the following:

class A 
{
    public function test(A $a) {}
}

class B extends A 
{
    public function test(B $b) {}
}

$b = new B();

Allowing subclasses as typehints would break LSP. There's a lot of theory on the why and how, if you're interested you should lookup "variance and contra variance".

Anyways the fix here is to still use the spatie Media class as type hint, and not your own implementation, you will be able to use your implementation, but you're not able to type hint it.

use Spatie\MediaLibrary\Media;

// ...

public function getPath(Media $media) : string { ... }

@brendt Thanks for the explanation of this issue. However i still can't use the implementation of the custom model from the custom path generator.

When trying to get an attribute from a relation ie $media->fileGroup->name i get Trying to get property 'name' of non-object

If i replace the $media variable with a new instance of my custom media model $media = \App\Models\Media::find($media->id) then it works fine ?

Have you correctly set your custom media model in the config? https://github.com/spatie/laravel-medialibrary/blob/master/config/medialibrary.php#L26

Yeah i have configured that to use the model in AppModels. The definition of which is:

use Spatie\MediaLibrary\Models\Media as BaseMedia;

class Media extends BaseMedia 

@jwtea and can I see your generator class?

``` namespace AppSpatieMediaLibrary;

use SpatieMediaLibraryModelsMedia;
use AppModelsProduct;

class ModelPathGenerator extends SpatieMediaLibraryPathGeneratorBasePathGenerator
{
/**
* Store the category for the given media
*
* @var String
*/
protected $category;

/**
 * Store the type for the given media
 *
 * @var String
 */
protected $type; 

/**
 * Get the path for the given media, relative to the root storage path.
 *
 * @param \Spatie\MediaLibrary\Models\Media $media
 *
 * @return string
 */
public function getPath(Media $media): string 
{
    //@todo
    $media = \App\Models\Media::find($media->id);
    if(is_a($media->model, Product::class)) {

        $this->category = $this->sanitizeForFS($media->model->category->name);
        $this->type = $this->sanitizeForFS($media->model->type->name);

        if($media->collection_name == 'main_image'){
            return $this->productMainImagePath($media);
        } else {
            return $this->productRepositoryImagePath($media);
        }

    } else {
        return $media->model_type.'/'.$this->getBasePath($media).'/';
    }
}

/**
 * Get base path
 * @param Media $media
 * @return string
 */
protected function getBasePath(Media $media): string
{
    return $media->file_name;
}

/**
 * Strip any characters out that we dont want for storing to fs
 *
 * @param String $string
 * @return string
 */
protected function sanitizeForFS(String $string): string
{
    return  preg_replace( '/[^a-z0-9]+/', '-', strtolower($string));
}

/**
 * Return path for a products main image.
 *
 * @param Media $media
 * @return string
 */
protected function productMainImagePath(Media $media): string 
{
    return $media->model_type.'/'.$this->category.'/'.$this->type.'/'.$media->model->id
        .'/main_image/'.$this->getBasePath($media).'/';
}
/**
 * Create file path for storign images stored under a product 
 *
 * @param Media $media
 * @return string
 */
protected function productRepositoryImagePath(Media $media): string
{
    return $media->model_type.'/'.$this->category.'/'.$this->type.'/'.$media->model->id.'/'
        .$media->fileGroup->name.'/'.$this->getBasePath($media).'/';
}

}

So this issue is no longer persisting, I rebuild the vagrant box i was using and installed again and it's started working. So not sure what is happening. Thank you for the time, i'll update one here if it comes back.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

denitsa-md picture denitsa-md  路  3Comments

stayallive picture stayallive  路  4Comments

ideadx picture ideadx  路  4Comments

mohammad6006 picture mohammad6006  路  4Comments

eichgi picture eichgi  路  3Comments