Installation:
composer require drewroberts/media
php artisan vendor:publish --provider="DrewRoberts\Media\MediaServiceProvider"
This publishes the migration files and config. Run migrations:
php artisan migrate
Configure Cloudinary:
Add credentials to .env:
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
First Use Case:
Upload an image via a model (e.g., Post):
use DrewRoberts\Media\Models\Image;
$post = new Post();
$post->title = "My Post";
$post->save();
$image = $post->images()->create([
'path' => 'path/to/local/file.jpg',
'alt' => 'Post thumbnail',
'title' => 'Thumbnail',
]);
The package automatically uploads to Cloudinary and stores metadata in the DB.
Attach media to Eloquent models via polymorphic relationships:
// In Post model
public function images()
{
return $this->morphMany(Image::class, 'model');
}
public function videos()
{
return $this->morphMany(Video::class, 'model');
}
Use MediaManager for batch operations:
use DrewRoberts\Media\Facades\MediaManager;
$images = MediaManager::uploadMultiple(
request()->file('images'),
'posts/123',
['alt' => 'Gallery images']
);
Assign tags to media for categorization:
$image->tags()->attach([1, 2, 3]); // Attach existing tags
$image->tags()->create(['name' => 'New Tag']); // Create new tag
Store YouTube videos with metadata:
$video = $post->videos()->create([
'url' => 'https://youtu.be/abc123',
'title' => 'Embedded Video',
'description' => 'Video description',
]);
Generate Cloudinary transformations on-the-fly:
$image->transform('fill', 300, 200)->getUrl();
// Outputs: https://res.cloudinary.com/.../fill_300_200/image.jpg
Use local storage as a fallback (configure in config/media.php):
'fallback' => [
'driver' => 'local',
'path' => storage_path('app/public/media'),
],
Validate and handle file uploads in a FormRequest:
public function rules()
{
return [
'images.*' => 'required|image|mimes:jpeg,png,jpg|max:2048',
];
}
public function withValidator($validator)
{
$validator->after(function ($validator) {
if ($validator->errors()->has('images')) {
return;
}
// Process uploads
});
}
Return media URLs with transformations:
return response()->json([
'image_url' => $image->transform('crop', 500, 500)->getUrl(),
'thumbnail_url' => $image->transform('thumbnail', 100, 100)->getUrl(),
]);
Use with Laravel Nova or Filament for media management:
// Nova Resource
public static $mediaFields = [
MediaField::make('Images', 'images'),
MediaField::make('Videos', 'videos'),
];
Cache transformed URLs to reduce Cloudinary API calls:
$url = Cache::remember("media_{$image->id}_transformed", now()->addHours(1), function () use ($image) {
return $image->transform('fill', 300, 200)->getUrl();
});
Cloudinary Credentials:
CLOUDINARY_API_SECRET will cause silent failures.php artisan media:test-connection
Polymorphic Relationships:
morphMap in AppServiceProvider can cause issues:
public function boot()
{
\DrewRoberts\Media\Models\Image::morphMap([
'post' => \App\Models\Post::class,
// Add other models
]);
}
File Size Limits:
if ($file->getSize() > 50 * 1024 * 1024) { // 50MB
Log::error("File too large: {$file->getClientOriginalName()}");
}
YouTube URL Parsing:
youtu.be) may break video storage.$url = preg_replace('/^(https?:\/\/)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)\//i', 'https://youtu.be/', $url);
Local Fallback:
storage:link to be run:
php artisan storage:link
Log Uploads:
Enable debug mode in config/media.php:
'debug' => env('MEDIA_DEBUG', false),
Check logs for upload errors:
tail -f storage/logs/laravel.log | grep media
Cloudinary API Errors:
failed_jobs table for queued jobs that failed.Database Issues:
php artisan media:prune to clean up orphaned media records.Custom Transformations:
Extend the DrewRoberts\Media\Transformations\Transformation class to add custom Cloudinary transformations:
namespace App\Media\Transformations;
use DrewRoberts\Media\Transformations\Transformation;
class CustomTransformation extends Transformation
{
public function __construct($width, $height, $effect)
{
$this->width = $width;
$this->height = $height;
$this->effect = $effect;
}
public function getTransformation()
{
return "e_{$this->effect}:w_{$this->width}_h_{$this->height}";
}
}
Custom Storage Drivers:
Implement DrewRoberts\Media\Contracts\StorageDriver for non-Cloudinary storage (e.g., AWS S3):
namespace App\Media\Drivers;
use DrewRoberts\Media\Contracts\StorageDriver;
class S3Driver implements StorageDriver
{
public function upload($file, $path, $options)
{
// Custom S3 upload logic
}
public function getUrl($path, $options = [])
{
// Custom URL generation
}
}
Events: Listen to media events for custom logic:
// In EventServiceProvider
protected $listen = [
'DrewRoberts\Media\Events\MediaUploaded' => [
\App\Listeners\LogMediaUpload::class,
],
];
4. **Middleware**:
Restrict media access with middleware:
```php
namespace App\Http\Middleware;
use Closure;
use DrewRoberts\Media\Models\Image;
class VerifyMediaOwnership
{
public function handle($request, Closure $next)
{
$image = Image::findOrFail($request->route('image'));
if ($image->model->user_id !== auth()->id()) {
abort(403);
}
return $next($request);
}
}
How can I help you explore Laravel packages today?