├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src ├── Providers └── EloquentDynamicPhotosServiceProvider.php ├── Traits └── HasPhotos.php └── config └── eloquent_photo.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Behzad Soltanpour 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Eloquent Photos 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/behzadsp/eloquent-dynamic-photos.svg?style=flat-square)](https://packagist.org/packages/behzadsp/eloquent-dynamic-photos) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/behzadsp/eloquent-dynamic-photos.svg?style=flat-square)](https://packagist.org/packages/behzadsp/eloquent-dynamic-photos) 5 | 6 | This is a Laravel Eloquent trait that provides an easy and dynamic way to manage photos in your Eloquent models. 7 | 8 | ## Installation 9 | 10 | You can install the package via composer: 11 | 12 | ```bash 13 | composer require behzadsp/eloquent-dynamic-photos 14 | ``` 15 | 16 | You can publish the config file with: 17 | 18 | ```bash 19 | php artisan vendor:publish --provider="Behzadsp\EloquentDynamicPhotos\Providers\EloquentDynamicPhotosServiceProvider" 20 | ``` 21 | 22 | This is the contents of the global configurations for uploading images. 23 | 24 | ```php 25 | 'public', // Disk to use for storing photos 29 | 'root_directory' => 'images', // Root directory for photos 30 | 'name_attribute' => 'slug', // Model attribute used for file name 31 | 'quality' => 50, // Quality for encoding the photos 32 | 'format' => 'webp', // Format of the stored photos 33 | 'slug_limit' => 240, // Name limit to save in database 34 | 'timestamp_format' => 'U', // U represents Unix timestamp 35 | ]; 36 | 37 | ``` 38 | 39 | ## Usage 40 | 41 | After installing the package, simply use the `HasPhotos` trait in your Eloquent models: 42 | 43 | ```php 44 | use Behzadsp\EloquentDynamicPhotos\Traits\HasPhotos; 45 | 46 | class YourModel extends Model 47 | { 48 | use HasPhotos; 49 | 50 | // ... 51 | } 52 | ``` 53 | 54 | Of course, you can override certain config in individual model by declaring the corresponding method. Like below: 55 | 56 | ```php 57 | class User extends Model 58 | { 59 | use HasPhotos; 60 | 61 | protected function eloquentPhotoDisk() 62 | { 63 | return 'user-avatar'; 64 | } 65 | 66 | protected function eloquentPhotoFormat() 67 | { 68 | return 'png'; 69 | } 70 | 71 | protected function eloquentPhotoRootDirectory() 72 | { 73 | return 'images'; 74 | } 75 | 76 | protected function eloquentPhotoQuality() 77 | { 78 | return '50'; 79 | } 80 | 81 | protected function eloquentPhotoNameAttribute() 82 | { 83 | return 'slug'; 84 | } 85 | 86 | protected function eloquentPhotoSlugLimit() 87 | { 88 | return '240'; 89 | } 90 | 91 | protected function eloquentPhotoTimestampFormat() 92 | { 93 | return 'U'; 94 | } 95 | } 96 | ``` 97 | 98 | You can now use the methods provided by the trait in your models: 99 | 100 | ```php 101 | $model = YourModel::first(); 102 | 103 | // delete photo file only and not database column. 104 | $model->deletePhotoFile('photo_field'); 105 | 106 | // update photo file and save it to database column. 107 | $model->updatePhoto($photo, 'photo_field'); 108 | 109 | // get full photo path 110 | $model->getPhotoFullPath('photo_path'); 111 | 112 | // get photo directory path 113 | $model->getPhotoDirectoryPath(); 114 | 115 | // get photo URL 116 | $model->photo_field_url; 117 | ``` 118 | 119 | ## Testing 120 | 121 | ```bash 122 | composer test 123 | ``` 124 | 125 | ## License 126 | 127 | The MIT License (MIT). Please see [License File](https://github.com/behzadsp/eloquent-dynamic-photos/blob/main/LICENSE) for more information. 128 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "behzadsp/eloquent-dynamic-photos", 3 | "description": "A Laravel Eloquent trait for dynamically handling and managing photo storage with ease.", 4 | "license": "MIT", 5 | "homepage": "https://github.com/behzadsp/eloquent-dynamic-photos", 6 | "keywords": [ 7 | "behzadsp", 8 | "eloquent", 9 | "dynamic", 10 | "photo", 11 | "image" 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "Behzadsp\\EloquentDynamicPhotos\\": "src/" 16 | } 17 | }, 18 | "authors": [ 19 | { 20 | "name": "Behzad Soltanpour" 21 | } 22 | ], 23 | "require": { 24 | "php": ">=7.4", 25 | "ext-gd": "*", 26 | "illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0", 27 | "illuminate/database": "^7.0|^8.0|^9.0|^10.0|^11.0", 28 | "illuminate/filesystem": "^7.0|^8.0|^9.0|^10.0|^11.0", 29 | "nesbot/carbon": "^2.53", 30 | "intervention/image": "^2.7" 31 | }, 32 | "config": { 33 | "sort-packages": true 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "Behzadsp\\EloquentDynamicPhotos\\Providers\\EloquentDynamicPhotosServiceProvider" 39 | ] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Providers/EloquentDynamicPhotosServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 12 | __DIR__.'/../config/eloquent_photo.php' => config_path('eloquent_photo.php'), 13 | ], 'config'); 14 | 15 | // Load default configuration 16 | $this->mergeConfigFrom(__DIR__.'/../config/eloquent_photo.php', 'eloquent_photo'); 17 | } 18 | 19 | public function register() 20 | { 21 | // 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Traits/HasPhotos.php: -------------------------------------------------------------------------------- 1 | $photoField) { 16 | Storage::disk($this->getEloquentPhotoDisk())->delete( 17 | $this->$photoField, 18 | ); 19 | 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | public function updatePhoto($photo, string $photoField): Model 27 | { 28 | $this->deletePhotoFile($photoField); 29 | 30 | if ( 31 | !Storage::disk($this->getEloquentPhotoDisk())->exists( 32 | $this->getEloquentPhotoRootDirectory() . 33 | '/' . 34 | $this->getDirName(), 35 | ) 36 | ) { 37 | Storage::disk($this->getEloquentPhotoDisk())->makeDirectory( 38 | $this->getEloquentPhotoRootDirectory() . 39 | '/' . 40 | $this->getDirName(), 41 | ); 42 | } 43 | 44 | $photoPath = $this->getPhotoDirectoryPath(); 45 | 46 | $image = Image::make($photo) 47 | ->encode( 48 | $this->getEloquentPhotoFormat(), 49 | $this->getEloquentPhotoQuality() 50 | ); 51 | 52 | Storage::disk($this->getEloquentPhotoDisk()) 53 | ->put($photoPath, $image->stream($this->getEloquentPhotoFormat(), $this->getEloquentPhotoQuality())); 54 | 55 | $this->$photoField = $photoPath; 56 | 57 | $this->saveOrFail(); 58 | 59 | return $this; 60 | } 61 | 62 | public function getPhotoFullPath(string $photoPath) 63 | { 64 | return Storage::disk($this->getEloquentPhotoDisk())->path($photoPath); 65 | } 66 | 67 | public function getPhotoDirectoryPath() 68 | { 69 | return $this->getEloquentPhotoRootDirectory() . 70 | '/' . 71 | $this->getDirName() . 72 | '/' . 73 | $this->getFileName() . 74 | '.' . 75 | $this->getEloquentPhotoFormat(); 76 | } 77 | 78 | public function getFileName() 79 | { 80 | $nameAttribute = $this->getEloquentPhotoNameAttribute(); 81 | 82 | return str($this->$nameAttribute) 83 | ->limit($this->getEloquentPhotoSlugLimit()) 84 | ->toString() . 85 | '_' . 86 | Str::random(5) . 87 | '_'. 88 | Carbon::now()->format($this->getEloquentPhotoTimestampFormat()); 89 | } 90 | 91 | public function getDirName(): string 92 | { 93 | return str(class_basename($this)) 94 | ->plural() 95 | ->lower() 96 | ->toString(); 97 | } 98 | 99 | public function getAttribute($key) 100 | { 101 | if (str_ends_with($key, '_url')) { 102 | $photoField = str_replace('_url', '', $key); 103 | 104 | if (array_key_exists($photoField, $this->attributes)) { 105 | if ($this->attributes[$photoField] === null) { 106 | return null; 107 | } 108 | 109 | return Storage::disk($this->getEloquentPhotoDisk())->url( 110 | $this->attributes[$photoField], 111 | ); 112 | } 113 | } 114 | 115 | return parent::getAttribute($key); 116 | } 117 | 118 | protected function getEloquentPhotoDisk(): string 119 | { 120 | if (method_exists($this, 'eloquentPhotoDisk')) { 121 | return $this->eloquentPhotoDisk(); 122 | } 123 | 124 | return config('eloquent_photo.disk'); 125 | } 126 | 127 | protected function getEloquentPhotoRootDirectory(): string 128 | { 129 | if (method_exists($this, 'eloquentPhotoRootDirectory')) { 130 | return $this->eloquentPhotoRootDirectory(); 131 | } 132 | 133 | return config('eloquent_photo.root_directory'); 134 | } 135 | 136 | protected function getEloquentPhotoFormat(): string 137 | { 138 | if (method_exists($this, 'eloquentPhotoFormat')) { 139 | return $this->eloquentPhotoFormat(); 140 | } 141 | 142 | return config('eloquent_photo.format'); 143 | } 144 | 145 | protected function getEloquentPhotoQuality(): string 146 | { 147 | if (method_exists($this, 'eloquentPhotoQuality')) { 148 | return $this->eloquentPhotoQuality(); 149 | } 150 | 151 | return config('eloquent_photo.quality'); 152 | } 153 | 154 | protected function getEloquentPhotoNameAttribute(): string 155 | { 156 | if (method_exists($this, 'eloquentPhotoNameAttribute')) { 157 | return $this->eloquentPhotoNameAttribute(); 158 | } 159 | 160 | return config('eloquent_photo.name_attribute'); 161 | } 162 | 163 | protected function getEloquentPhotoSlugLimit(): string 164 | { 165 | if (method_exists($this, 'eloquentPhotoSlugLimit')) { 166 | return $this->eloquentPhotoSlugLimit(); 167 | } 168 | 169 | return config('eloquent_photo.slug_limit'); 170 | } 171 | 172 | protected function getEloquentPhotoTimestampFormat(): string 173 | { 174 | if (method_exists($this, 'eloquentPhotoTimestampFormat')) { 175 | return $this->eloquentTimestampFormat(); 176 | } 177 | 178 | return config('eloquent_photo.timestamp_format'); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/config/eloquent_photo.php: -------------------------------------------------------------------------------- 1 | 'public', 5 | 'name_attribute' => 'slug', 6 | 'root_directory' => 'images', 7 | 'quality' => 50, 8 | 'format' => 'webp', 9 | 'slug_limit' => 240, 10 | 'timestamp_format' => 'U', // U represents Unix timestamp 11 | ]; 12 | --------------------------------------------------------------------------------