├── .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 | [![Become a Patron](https://img.shields.io/badge/Becoma%20a-Patron-f96854.svg?style=for-the-badge)](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 | --------------------------------------------------------------------------------