├── .github
└── FUNDING.yml
├── .gitignore
├── .php_cs.dist
├── LICENSE.md
├── README.md
├── baseline.xml
├── composer.json
├── config
└── picasso.php
├── psalm.xml
├── samples
└── picasso-manifest.json
└── src
├── Dimensions.php
├── Engine.php
├── Facade.php
├── Manifest.php
├── Picasso.php
├── ServiceProvider.php
└── Storage.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | patreon: laravelista
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | vendor/
3 | .php_cs.cache
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | in(__DIR__)
5 | ->exclude(['.github','vendor', 'samples'])
6 | ->name('*.php')
7 | ->notName('*.blade.php')
8 | ->ignoreDotFiles(true)
9 | ->ignoreVCS(true);
10 |
11 | return PhpCsFixer\Config::create()
12 | ->setRules([
13 | '@PSR1' => true,
14 | '@PSR12' => true,
15 | 'array_syntax' => ['syntax' => 'short'],
16 | 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'],
17 | 'no_unused_imports' => true,
18 | ])
19 | ->setFinder($finder);
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-2021 Mario Bašić
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Picasso
2 |
3 | Picasso is a Laravel Image Management and Optimization Package. Define image dimensions and options, store uploaded image in multiple dimensions with or without a watermark and retrieve optimized images on your website when needed.
4 |
5 | [](https://www.patreon.com/laravelista)
6 |
7 | ## Overview
8 |
9 | To reduce site size and improve site loading time, this packages enables you to:
10 |
11 | - resize image to multiple dimensions; _[width x height with upscale]_ ('news_thumbnail', 'news_gallery', 'news_cover', ...)
12 | - retrieve optimized image for specific position `get('news_cover')`
13 | - apply watermark on images that need it **(task in progress)**
14 | - quickly change the image dimension and update all, subset or a single image with optimized size
15 | - implement this package in any phase of your application
16 | - to use this package on your whole site or just a part of it
17 | - set global or individual image quality (default **60**)
18 | - set global or individual image format (default **webp**)
19 |
20 | ### How it works
21 |
22 | In the config file you define the dimension width and height, (format and quality are optional) and unique name for that dimension. In your controller call `$picasso->optimize('images/image.jpg', ['home_slideshow_large', 'home_slideshow_thumbnail']')`. This method takes the original image, optimizes it according to the entered dimensions, saves it to storage and saves the record to the manifest file
23 |
24 | Later, when you call `Picasso::get('images/image.jpg', 'home_slideshow_large')` you will get the optimized image.
25 |
26 | ### Benefits
27 |
28 | You can keep your original user uploaded images untouched (2MB or more). This package will create new optimized images and keep reference of the original and optimized in the manifest file.
29 |
30 | Your page will load faster because it will have less MB to download because the images will be smaller. I have managed to reduce image size from 2.4MB to 700Kb, just by implementing this package as an addon later in the development phase.
31 |
32 | ## Installation
33 |
34 | From the command line:
35 |
36 | ```
37 | composer require laravelista/picasso
38 | ```
39 |
40 | Publish the config file `picasso.php` to your `/config` directory:
41 |
42 | ```
43 | php artisan vendor:publish --provider="Laravelista\Picasso\ServiceProvider" --tag=config
44 | ```
45 |
46 | Installation complete!
47 |
48 | ## Configuration
49 |
50 | Before continuing be sure to open the `/config/picasso.php` file and update the dimensions and quality to your needs.
51 |
52 | ## Usage
53 |
54 | There are a few ways to implement this package in your application. I will try to cover them all.
55 |
56 | ### Store method
57 |
58 | After you have stored the user uploaded image in your storage `UploadedFile $image->store('images')` and you have retrieved the path to the image. Give that path (that you would usually store in the database) to picasso:
59 |
60 | ```
61 | use Laravelista\Picasso\Picasso;
62 |
63 | public function store(Request $request, Picasso $picasso)
64 | {
65 | // ...
66 |
67 | // store original image in storage
68 | $article->image = $request->image->store('images');
69 |
70 | // optimize original image to desired dimensions
71 | $picasso->optimize($article->image, ['news_small', 'news_cover']);
72 |
73 | // ...
74 | }
75 | ```
76 |
77 | ### Update method
78 |
79 | When the user is going to replace the existing image with a new one, we have to first purge all records from storage and manifest file of the old image and then optimize the new image:
80 |
81 | ```
82 | use Laravelista\Picasso\Picasso;
83 |
84 | public function update(Request $request, Article $article, Picasso $picasso)
85 | {
86 | // ...
87 |
88 | if ($request->hasFile('image')) {
89 |
90 | // delete original image from storage
91 | Storage::delete($article->image);
92 |
93 | // delete all optimized images for old image
94 | $picasso->drop($article->image, ['news_small', 'news_cover']);
95 |
96 | // save new original image to storage and retrieve the path
97 | $article->image = $request->image->store('images');
98 |
99 | // optimize new original image
100 | $picasso->optimize($article->image, ['news_small', 'news_cover']);
101 | }
102 |
103 | // ...
104 | }
105 | ```
106 |
107 | ### Destroy method
108 |
109 | When deleting a record which has optimized images, be sure to delete optimized image also to reduce unused files:
110 |
111 | ```
112 | use Laravelista\Picasso\Picasso;
113 |
114 | public function destroy(Article $article, Picasso $picasso)
115 | {
116 | // ...
117 |
118 | // delete original image
119 | Storage::delete($article->image);
120 |
121 | // delete optimized images
122 | $picasso->purge($article->image);
123 |
124 | // delete record from database
125 | $article->delete();
126 |
127 | // ...
128 | }
129 | ```
130 |
131 | ### Optimizing already uploaded and saved images
132 |
133 | My suggestion is to create a console route for this. I will show you how I do this in my applications. In `routes/console.php` and this route:
134 |
135 | ```
136 | use Laravelista\Picasso\Picasso;
137 |
138 | Artisan::command('picasso:article-optimize', function (Picasso $picasso) {
139 |
140 | $images = Article::all()->pluck('image')->toArray();
141 |
142 | $picasso->optimize($images, ['news_small', 'news_cover']);
143 |
144 | $this->comment("Article images optimized!");
145 | });
146 | ```
147 |
148 | Now from the command line you can call `php artisan picasso:article-optimize` whenever you want and it will grab the original images for table article, created optimized images, create/update optimized images in storage and update the reference in the database.
149 |
150 | ### Retrieving optimized images
151 |
152 | From your view files do:
153 |
154 | ```
155 |
156 | ```
157 |
158 | This line will retrieve the optimized image URL.
159 |
160 | ## API
161 |
162 | For now, there are only four main methods in Picasso:
163 |
164 | ### `optimize(string|array $image, string|array $dimension, string $disk = null)`
165 |
166 | This method creates optimized images in desired dimensions for given images or image.
167 |
168 | It accepts an array of image paths or a single image path.
169 | It accepts an array of valid dimensions (as defined in the configuration) or a single dimension
170 | The last parameter is the disk where to save the optimized image.
171 |
172 | ### `get(string $image, string $dimension, string $disk = null)`
173 |
174 | This method retrieves the optimized image for given original image path and desired dimension.
175 |
176 | The last parameter is the disk on which to perform this operation.
177 |
178 | ### `drop(string $image, string|array $dimension, string $disk = null)`
179 |
180 | Thi method deletes optimized images from storage for given image path and dimension or dimensions.
181 |
182 | The last parameter is the disk on which to perform this operation.
183 |
184 | ### `purge(string $image, string $disk = null)`
185 |
186 | Thi method deletes all optimized images from storage for given image path.
187 |
188 | The last parameter is the disk on which to perform this operation.
189 |
190 | ## Development
191 |
192 | ```bash
193 | # Install dependencies
194 | composer install
195 |
196 | # Run Psalm
197 | vendor/bin/psalm
198 |
199 | # Format code
200 | vendor/bin/php-cs-fixer fix
201 | ```
202 |
203 | ## Sponsors & Backers
204 |
205 | I would like to extend my thanks to the following sponsors & backers for funding my open-source journey. If you are interested in becoming a sponsor or backer, please visit the [Backers page](https://mariobasic.com/backers).
206 |
207 | ## Contributing
208 |
209 | Thank you for considering contributing to Picasso! The contribution guide can be found [Here](https://mariobasic.com/contributing).
210 |
211 | ## Code of Conduct
212 |
213 | In order to ensure that the open-source community is welcoming to all, please review and abide by the [Code of Conduct](https://mariobasic.com/code-of-conduct).
214 |
215 | ## License
216 |
217 | Picasso is open-source software licensed under the [MIT license](https://opensource.org/licenses/MIT).
--------------------------------------------------------------------------------
/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | has
6 |
7 |
8 |
9 |
10 | url
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravelista/picasso",
3 | "description": "Laravel Image Management and Optimization Package",
4 | "keywords": ["laravel", "image"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Mario Bašić",
9 | "email": "mario@laravelista.com",
10 | "homepage": "https://mariobasic.com"
11 | }
12 | ],
13 | "autoload": {
14 | "psr-4": {
15 | "Laravelista\\Picasso\\": "src/"
16 | }
17 | },
18 | "require": {
19 | "illuminate/support": "^8.0",
20 | "illuminate/database": "^8.0",
21 | "illuminate/filesystem": "^8.0",
22 | "intervention/image": "^2.5",
23 | "php": "^8.0"
24 | },
25 | "require-dev": {
26 | "vimeo/psalm": "^4.6",
27 | "friendsofphp/php-cs-fixer": "^2.18"
28 | },
29 | "extra": {
30 | "laravel": {
31 | "providers": [
32 | "Laravelista\\Picasso\\ServiceProvider"
33 | ],
34 | "aliases": {
35 | "Picasso": "Laravelista\\Picasso\\Facade"
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/config/picasso.php:
--------------------------------------------------------------------------------
1 | [
5 |
6 | // Replace this with your own dimension requirements
7 | 'example_dimension_name' => [
8 | 'width' => 1920,
9 | 'height' => 1080
10 | ],
11 |
12 | // Dot notation example.
13 | // Usage: frontend.categories.index
14 | 'frontend' => [
15 | 'categories' => [
16 | 'index' => [
17 | 'width' => 1280,
18 | 'height' => 720,
19 | 'format' => 'png'
20 | ],
21 | 'show' => [
22 | 'width' => 1920,
23 | 'height' => 1080,
24 | 'quality' => 90
25 | ],
26 | ]
27 | ]
28 |
29 | // ...
30 | ],
31 |
32 | // Use this variable to set the default image quality.
33 | // 0-100
34 | 'quality' => 60,
35 |
36 | /**
37 | * The readable image formats depend on the choosen driver (GD or Imagick) and your local configuration.
38 | * By default Intervention Image currently supports the following major formats.
39 | *
40 | * Image Formats:
41 | * JPEG, PNG, GIF, TIF, BMP, ICO, PSD, WebP*
42 | *
43 | * For WebP support GD driver must be used with PHP 5 >= 5.5.0 or PHP 7 in order to use imagewebp().
44 | * If Imagick is used, it must be compiled with libwebp for WebP support.
45 | */
46 | 'format' => 'webp',
47 |
48 | /**
49 | * This the the place where you should keep your original full size images.
50 | *
51 | * In your Laravel application in `config/filesystem.php`, create a new disk under `disks`:
52 | *
53 | * 'originals' => [
54 | * 'driver' => 'local',
55 | * 'root' => storage_path('app/originals'),
56 | * ],
57 | *
58 | * Or replace with the disk name where your original are located.
59 | *
60 | */
61 | 'disk' => 'originals'
62 | ];
63 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/samples/picasso-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "images/destinations/cities/zadar/index.jpg": {
3 | "destinations.cover": {
4 | "src": "images/destinations/cities/zadar/index.jpg-destinations.cover.webp",
5 | "format": "webp",
6 | "quality": 60,
7 | "width": 1600,
8 | "height": 1067
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Dimensions.php:
--------------------------------------------------------------------------------
1 | collection = Config::get(key: 'picasso.dimensions');
15 | }
16 |
17 | /**
18 | * Check if the dimension exists in the config.
19 | *
20 | * @throws \Exception
21 | */
22 | public function hasOrFail(string|array $dimension): void
23 | {
24 | if (!Arr::has(
25 | array: $this->collection,
26 | keys: $dimension
27 | )) {
28 | throw new \Exception(
29 | message: 'Dimension not valid. Check `config/picasso.php` for supported dimensions.'
30 | );
31 | }
32 | }
33 |
34 | public function getWidth(string $dimension): int
35 | {
36 | return Arr::get(
37 | array: $this->collection,
38 | key: $dimension . '.width'
39 | );
40 | }
41 |
42 | public function getHeight(string $dimension): int
43 | {
44 | return Arr::get(
45 | array: $this->collection,
46 | key: $dimension . '.height'
47 | );
48 | }
49 |
50 | public function getQuality(string $dimension): ?int
51 | {
52 | return Arr::get(
53 | array: $this->collection,
54 | key: $dimension . '.quality'
55 | );
56 | }
57 |
58 | public function getFormat(string $dimension): ?string
59 | {
60 | return Arr::get(
61 | array: $this->collection,
62 | key: $dimension . '.format'
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Engine.php:
--------------------------------------------------------------------------------
1 | quality = Config::get('picasso.quality');
17 | $this->format = Config::get('picasso.format');
18 | }
19 |
20 | public function setQuality(int $quality): void
21 | {
22 | $this->quality = $quality;
23 | }
24 |
25 | public function getQuality(string $dimension): int
26 | {
27 | return $this->dimensions->getQuality(dimension: $dimension) ?? $this->quality;
28 | }
29 |
30 | public function setFormat(string $format): void
31 | {
32 | $this->format = $format;
33 | }
34 |
35 | public function getFormat(string $dimension): string
36 | {
37 | return $this->dimensions->getFormat(dimension: $dimension) ?? $this->format;
38 | }
39 |
40 | public function manipulate(string $image, string $dimension): string
41 | {
42 | return $this->manager
43 | ->make($image)
44 | ->fit(
45 | $this->dimensions->getWidth(dimension: $dimension),
46 | $this->dimensions->getHeight(dimension: $dimension)
47 | )
48 | ->encode(
49 | $this->getFormat(dimension: $dimension),
50 | $this->getQuality(dimension: $dimension)
51 | )
52 | ->__toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Facade.php:
--------------------------------------------------------------------------------
1 | manifest = $this->load();
16 | }
17 |
18 | protected function load(): array
19 | {
20 | if (IlluminateStorage::disk(name: 'local')->has(self::FILENAME)) {
21 | return json_decode(
22 | IlluminateStorage::disk(name: 'local')->get(path: self::FILENAME),
23 | true
24 | );
25 | }
26 |
27 | return [];
28 | }
29 |
30 | protected function save(): void
31 | {
32 | IlluminateStorage::disk(name: 'local')->put(
33 | path: self::FILENAME,
34 | contents: json_encode(value: $this->manifest)
35 | );
36 | }
37 |
38 | public function update(
39 | string $image,
40 | string $dimension,
41 | string $optimized_image,
42 | string $format,
43 | int $quality
44 | ): void {
45 | $this->manifest[$image][$dimension] = [
46 | "src" => $optimized_image,
47 | "format" => $format,
48 | "quality" => $quality,
49 | "width" => $this->dimensions->getWidth(dimension: $dimension),
50 | "height" => $this->dimensions->getHeight(dimension: $dimension)
51 | ];
52 |
53 | $this->save();
54 | }
55 |
56 | public function delete(string $image, string $dimension): void
57 | {
58 | unset($this->manifest[$image][$dimension]);
59 |
60 | $this->save();
61 | }
62 |
63 | public function get(string $image, ?string $dimension = null): ?array
64 | {
65 | if (is_null($dimension)) {
66 | if (!array_key_exists($image, $this->manifest)) {
67 | return null;
68 | }
69 |
70 | return $this->manifest[$image];
71 | }
72 |
73 | if (!array_key_exists($image, $this->manifest) or
74 | !array_key_exists($dimension, $this->manifest[$image])
75 | ) {
76 | return null;
77 | }
78 |
79 | return $this->manifest[$image][$dimension];
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Picasso.php:
--------------------------------------------------------------------------------
1 | storage->disk(disk: $disk);
21 | }
22 |
23 | public function optimize(
24 | string|array $image,
25 | string|array $dimension,
26 | ?string $disk = null
27 | ): void {
28 | if (is_array($image)) {
29 | for ($i = 0; $i < count($image); $i++) {
30 | $this->optimize(
31 | image: $image[$i],
32 | dimension: $dimension,
33 | disk: $disk
34 | );
35 | }
36 |
37 | return;
38 | }
39 |
40 | $this->dimensions->hasOrFail(dimension: $dimension);
41 |
42 | if (is_array($dimension)) {
43 | // Create one image per dimension.
44 | for ($i = 0; $i < count($dimension); $i++) {
45 | $this->process(
46 | image: $image,
47 | dimension: $dimension[$i],
48 | disk: $disk
49 | );
50 | }
51 | } else {
52 | $this->process(
53 | image: $image,
54 | dimension: $dimension,
55 | disk: $disk
56 | );
57 | }
58 | }
59 |
60 | protected function process(
61 | string $image,
62 | string $dimension,
63 | ?string $disk = null
64 | ): void {
65 | // Get image from storage for manipulation.
66 | $raw_image = $this->storage->get(path: $image);
67 |
68 | $optimized_image = $this->engine->manipulate(
69 | image: $raw_image,
70 | dimension: $dimension
71 | );
72 |
73 | $optimized_image_name = $this->getOptimizedImageName(
74 | image: $image,
75 | dimension: $dimension
76 | );
77 |
78 | // Save to storage.
79 | $this->storage->put(
80 | name: $optimized_image_name,
81 | content: $optimized_image,
82 | disk: $disk
83 | );
84 |
85 | // Update manifest of optimized images.
86 | $this->manifest->update(
87 | image: $image,
88 | dimension: $dimension,
89 | optimized_image: $optimized_image_name,
90 | format: $this->engine->getFormat(dimension: $dimension),
91 | quality: $this->engine->getQuality(dimension: $dimension)
92 | );
93 | }
94 |
95 | protected function getOptimizedImageName(
96 | string $image,
97 | string $dimension
98 | ): string {
99 | return "$image-$dimension.{$this->engine->getFormat(dimension: $dimension)}";
100 | }
101 |
102 | /**
103 | * It returns the optimized if for given dimension if any, or
104 | * it returns the original image and logs the missing optimized
105 | * image to the log file.
106 | */
107 | public function get(
108 | string $image,
109 | string $dimension,
110 | ?string $disk = null
111 | ): string {
112 | $this->dimensions->hasOrFail(dimension: $dimension);
113 |
114 | $record = $this->manifest->get(
115 | image: $image,
116 | dimension: $dimension
117 | );
118 |
119 | if (is_null($record)) {
120 | throw new \Exception(
121 | "Image not found! Image: {$image} Dimension: {$dimension} Disk: {$disk}"
122 | );
123 | }
124 |
125 | return $this->storage->url(
126 | path: $record['src'],
127 | disk: $disk
128 | );
129 | }
130 |
131 | public function drop(
132 | string $image,
133 | string|array $dimension,
134 | ?string $disk = null
135 | ): void {
136 | if (is_array($dimension)) {
137 | for ($i = 0; $i < count($dimension); $i++) {
138 | $this->drop(
139 | image: $image,
140 | dimension: $dimension[$i],
141 | disk: $disk
142 | );
143 | }
144 |
145 | return;
146 | }
147 |
148 | $record = $this->manifest->get(
149 | image: $image,
150 | dimension: $dimension
151 | );
152 |
153 | if (is_null($record)) {
154 | throw new \Exception(
155 | "Record not found! Image: {$image} Dimension: {$dimension} Disk: {$disk}"
156 | );
157 | }
158 |
159 | $this->storage->delete(
160 | path: $record['src'],
161 | disk: $disk
162 | );
163 |
164 | $this->manifest->delete(
165 | image: $image,
166 | dimension: $dimension
167 | );
168 | }
169 |
170 | public function purge(string $image, ?string $disk = null): void
171 | {
172 | $records = $this->manifest->get(image: $image) ?? [];
173 |
174 | foreach ($records as $dimension => $record) {
175 | $this->storage->delete(
176 | path: $record['src'],
177 | disk: $disk
178 | );
179 |
180 | $this->manifest->delete(
181 | image: $image,
182 | dimension: $dimension
183 | );
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes(
12 | paths: [
13 | __DIR__ . '/../config/picasso.php' => $this->app->configPath(path: 'picasso.php'),
14 | ],
15 | groups: 'config'
16 | );
17 | }
18 |
19 | public function register(): void
20 | {
21 | $this->mergeConfigFrom(
22 | path: __DIR__ . '/../config/picasso.php',
23 | key: 'picasso'
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Storage.php:
--------------------------------------------------------------------------------
1 | disk = Config::get(key: 'picasso.disk');
19 | }
20 |
21 | /**
22 | * It sets the disk.
23 | */
24 | public function disk(string $disk): void
25 | {
26 | $this->disk = $disk;
27 | }
28 |
29 | /**
30 | * It gets the image content from the disk
31 | * where the originals are located.
32 | */
33 | public function get(string $path): string
34 | {
35 | return IlluminateStorage::disk(name: $this->disk)->get(path: $path);
36 | }
37 |
38 | /**
39 | * It saves the image to the specified disk or
40 | * to the default application disk.
41 | */
42 | public function put(string $name, string $content, ?string $disk = null): void
43 | {
44 | IlluminateStorage::disk(name: $disk)->put(
45 | path: $name,
46 | contents: $content
47 | );
48 | }
49 |
50 | public function url(string $path, ?string $disk = null): string
51 | {
52 | return IlluminateStorage::disk(name: $disk)->url($path);
53 | }
54 |
55 | public function delete(string $path, ?string $disk = null): void
56 | {
57 | IlluminateStorage::disk(name: $disk)->delete(paths: $path);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------