Skip to content

Commit

Permalink
Feature/new custom delete fix spatie#3190 (spatie#3252)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjam-es authored Oct 24, 2023
1 parent ae3107b commit a639108
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 19 deletions.
5 changes: 5 additions & 0 deletions config/media-library.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
*/
'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,

/*
* The class that contains the strategy for determining how to remove files.
*/
'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class,

/*
* Here you can specify which path generator should be used for the given class.
*/
Expand Down
80 changes: 80 additions & 0 deletions docs/advanced-usage/using-a-custom-file-removal-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: Using a custom file removal strategy
weight: 5
---

By default, files will be stored inside a directory that uses the `id` of its `Media`-object as a name. Given this default file and folder structure, the `DefaultFileRemover` option simply gets the root folder name and deletes it.

In cases where you need to use a custom directory structure, the `DefaultFileRemover` can cause problems. For example, if you have a directory structure like this:


```
media
---- 2023/09
------ file.jpg
------ second.jpg
------ conversions
--------- file-small.jpg
--------- file-medium.jpg
--------- file-big.jpg
--------- second-small.jpg
--------- second-medium.jpg
--------- second-big.jpg
...
```

Using the `DefaultFileRemover` will delete the entire `2023` directory, which is not what you want. So we would use a custom file remover to delete only the files that are no longer needed.


### Extending file remover functionality


Let's take a look at the interface:

```php
<?php

namespace Spatie\MediaLibrary\Support\FileRemover;

use Illuminate\Contracts\Filesystem\Factory;
use Spatie\MediaLibrary\MediaCollections\Filesystem;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

interface FileRemover
{
public function __construct(Filesystem $mediaFileSystem, Factory $filesystem);

/*
* Remove all files relating to the media model.
*/
public function removeAllFiles(Media $media): void;

/*
* Remove responsive files relating to the media model.
*/
public function removeResponsiveImages(Media $media, string $conversionName): void;

/*
* Remove a file relating to the media model.
*/
public function removeFile(string $path, string $disk): void;

}

```
You may use create your own custom file remover by implementing the `FileRemover` interface.

### Here to help

There is also now a second option available within media library for file remover functionality. Based on the above directory structure, we can use `FileBaseFileRemover`.

```php
// config/media-library.php

/*
* The class that contains the strategy for determining how to remove files.
*/
'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\FileBaseFileRemover::class,
```

This strategy works by locating the exact path of the image and conversions, and explicitly removing those files only, instead of purging a base directory.
21 changes: 21 additions & 0 deletions src/MediaCollections/Exceptions/InvalidFileRemover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Spatie\MediaLibrary\MediaCollections\Exceptions;

use Exception;
use Spatie\MediaLibrary\Support\FileRemover\FileRemover;

class InvalidFileRemover extends Exception
{
public static function doesntExist(string $class): self
{
return new static("File remover class `{$class}` doesn't exist");
}

public static function doesNotImplementPathGenerator(string $class): self
{
$fileRemoverClass = FileRemover::class;

return new static("File remover class `{$class}` must implement `$fileRemoverClass}`");
}
}
25 changes: 6 additions & 19 deletions src/MediaCollections/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Support\Str;
use Spatie\MediaLibrary\Conversions\ConversionCollection;
use Spatie\MediaLibrary\Conversions\FileManipulator;
use Spatie\MediaLibrary\Support\FileRemover\FileRemoverFactory;
use Spatie\MediaLibrary\MediaCollections\Events\MediaHasBeenAdded;
use Spatie\MediaLibrary\MediaCollections\Exceptions\DiskCannotBeAccessed;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
Expand Down Expand Up @@ -215,30 +216,16 @@ public function copyFromMediaLibrary(Media $media, string $targetFile): string

public function removeAllFiles(Media $media): void
{
$mediaDirectory = $this->getMediaDirectory($media);

if ($media->disk !== $media->conversions_disk) {
$this->filesystem->disk($media->disk)->deleteDirectory($mediaDirectory);
}
$fileRemover = FileRemoverFactory::create($media);

$conversionsDirectory = $this->getMediaDirectory($media, 'conversions');

$responsiveImagesDirectory = $this->getMediaDirectory($media, 'responsiveImages');

collect([$mediaDirectory, $conversionsDirectory, $responsiveImagesDirectory])
->unique()
->each(function (string $directory) use ($media) {
try {
$this->filesystem->disk($media->conversions_disk)->deleteDirectory($directory);
} catch (Exception $exception) {
report($exception);
}
});
$fileRemover->removeAllFiles($media);
}

public function removeFile(Media $media, string $path): void
{
$this->filesystem->disk($media->disk)->delete($path);
$fileRemover = FileRemoverFactory::create($media);

$fileRemover->removeFile($path, $media->disk);
}

public function removeResponsiveImages(Media $media, string $conversionName = 'media_library_original'): void
Expand Down
57 changes: 57 additions & 0 deletions src/Support/FileRemover/DefaultFileRemover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Spatie\MediaLibrary\Support\FileRemover;

use Exception;
use Illuminate\Support\Str;
use Illuminate\Contracts\Filesystem\Factory;
use Spatie\MediaLibrary\MediaCollections\Filesystem;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class DefaultFileRemover implements FileRemover
{
public function __construct(protected Filesystem $mediaFileSystem, protected Factory $filesystem)
{}

public function removeAllFiles(Media $media): void
{
$mediaDirectory = $this->mediaFileSystem->getMediaDirectory($media);

if ($media->disk !== $media->conversions_disk) {
$this->filesystem->disk($media->disk)->deleteDirectory($mediaDirectory);
}

$conversionsDirectory = $this->mediaFileSystem->getMediaDirectory($media, 'conversions');

$responsiveImagesDirectory = $this->mediaFileSystem->getMediaDirectory($media, 'responsiveImages');

collect([$mediaDirectory, $conversionsDirectory, $responsiveImagesDirectory])
->unique()
->each(function (string $directory) use ($media) {
try {
$this->filesystem->disk($media->conversions_disk)->deleteDirectory($directory);
} catch (Exception $exception) {
report($exception);
}
});
}

public function removeResponsiveImages(Media $media, string $conversionName): void
{
$responsiveImagesDirectory = $this->mediaFileSystem->getResponsiveImagesDirectory($media);

$allFilePaths = $this->filesystem->disk($media->disk)->allFiles($responsiveImagesDirectory);

$responsiveImagePaths = array_filter(
$allFilePaths,
fn (string $path) => Str::contains($path, $conversionName)
);

$this->filesystem->disk($media->disk)->delete($responsiveImagePaths);
}

public function removeFile(string $path, string $disk): void
{
$this->filesystem->disk($disk)->delete($path);
}
}
39 changes: 39 additions & 0 deletions src/Support/FileRemover/FileBaseFileRemover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Spatie\MediaLibrary\Support\FileRemover;

use Illuminate\Contracts\Filesystem\Factory;
use Spatie\MediaLibrary\MediaCollections\Filesystem;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\Support\FileRemover\FileRemover;
use Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover;

class FileBaseFileRemover extends DefaultFileRemover implements FileRemover
{
public function __construct(protected Filesystem $mediaFileSystem, protected Factory $filesystem)
{}

public function removeAllFiles(Media $media): void
{
$this->removeFile($this->mediaFileSystem->getMediaDirectory($media). $media->file_name, $media->disk);

$this->removeConvertedImages($media);
}

public function removeConvertedImages(Media $media): void
{
collect($media->getMediaConversionNames())->each(function ($conversionName) use ($media) {
$this->removeFile(
path: $media->getPathRelativeToRoot($conversionName),
disk: $media->conversions_disk
);

$this->mediaFileSystem->removeResponsiveImages($media, $conversionName);
});
}

public function removeFile(string $path, string $disk): void
{
$this->filesystem->disk($disk)->delete($path);
}
}
28 changes: 28 additions & 0 deletions src/Support/FileRemover/FileRemover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Spatie\MediaLibrary\Support\FileRemover;

use Illuminate\Contracts\Filesystem\Factory;
use Spatie\MediaLibrary\MediaCollections\Filesystem;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

interface FileRemover
{
public function __construct(Filesystem $mediaFileSystem, Factory $filesystem);

/*
* Remove all files relating to the media model.
*/
public function removeAllFiles(Media $media): void;

/*
* Remove responsive files relating to the media model.
*/
public function removeResponsiveImages(Media $media, string $conversionName): void;

/*
* Remove a file relating to the media model.
*/
public function removeFile(string $path, string $disk): void;

}
29 changes: 29 additions & 0 deletions src/Support/FileRemover/FileRemoverFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Spatie\MediaLibrary\Support\FileRemover;

use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\MediaCollections\Exceptions\InvalidFileRemover;

class FileRemoverFactory
{
public static function create(Media $media): FileRemover
{
$fileRemoverClass = config('media-library.file_remover_class');

static::guardAgainstInvalidFileRemover($fileRemoverClass);

return app($fileRemoverClass);
}

protected static function guardAgainstInvalidFileRemover(string $fileRemoverClass): void
{
if (!class_exists($fileRemoverClass)) {
throw InvalidFileRemover::doesntExist($fileRemoverClass);
}

if (!is_subclass_of($fileRemoverClass, FileRemover::class)) {
throw InvalidFileRemover::doesNotImplementFileRemover($fileRemoverClass);
}
}
}
Loading

0 comments on commit a639108

Please sign in to comment.