dialloibrahima/laravel-model-media
A Laravel package for attaching media files to Eloquent models. Upload, store, and associate images or documents with your records through a simple, fluent API, keeping media handling clean and organized. Ideal for galleries, avatars, and file attachments.
composer require dialloibrahima/laravel-model-media
Look first at the package README and src/ directory. Since this is a small package (11 stars, no dependents), the trait source is the most reliable API reference. Confirm the exact trait namespace, config keys, and method names before wiring it into a model.
Publish any package config if available:
php artisan vendor:publish --tag=model-media-config
Check config/model-media.php or similar for the default disk, upload directory, and visibility settings.
Schema::table('users', function (Blueprint $table) {
$table->string('avatar')->nullable();
});
use Dialloibrahima\ModelMedia\Traits\HasMedia; // verify namespace in README
class User extends Model
{
use HasMedia;
protected $mediaAttributes = ['avatar']; // check exact property name
}
The package is best suited for simple “one file per attribute” needs: avatars, covers, thumbnails, PDFs, and similar single-file attachments where you do not need a full media library.
Single-file attributes. Treat each media column as a nullable string containing a relative path. Let the package handle storage concerns, but keep the model attribute as the source of truth. This keeps queries simple and avoids joins.
Replacing files. The common day-to-day pattern is replace-and-cleanup:
public function updateAvatar(Request $request, User $user)
{
$request->validate([
'avatar' => ['required', 'image', 'max:2048'],
]);
// Exact method names depend on the package; the pattern is:
// 1. delete or overwrite the previous file
// 2. store the new file
// 3. persist the new relative path
$user->update([
'avatar' => $request->file('avatar'),
]);
}
If the trait does not automatically delete the old file, do it explicitly before saving the replacement. Otherwise you will accumulate orphaned files.
URL accessors. Store relative paths, not full URLs. Generate URLs at read time so the disk or CDN can change without a data migration:
public function getAvatarUrlAttribute(): ?string
{
return $this->avatar
? Storage::disk(config('model-media.disk', 'public'))->url($this->avatar)
: null;
}
Then expose it through an API resource or $appends.
Multiple files without a media table. If you need a small gallery, use a JSON column cast to an array and let the package store each file path:
protected $casts = [
'gallery' => 'array',
];
Loop over uploaded files in the controller and append stored paths. This is the pragmatic extension point when one attribute is not enough but a full media table is overkill.
Disk strategy. Use the public disk for locally served files and S3-compatible disks for production. Keep the disk name in config, not hard-coded in controllers. For private files, return temporary URLs rather than public ones.
Validation and form requests. Validate file type, size, and dimensions before the trait touches storage. The package manages paths, not upload security. Use Laravel’s image, mimes, max, and dimensions rules.
Testing. Use Storage::fake() in feature tests and assert that the expected path was stored on the model:
Storage::fake('public');
$response = $this->post('/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg'),
]);
$this->assertNotNull($user->fresh()->avatar);
Storage::disk('public')->assertExists($user->fresh()->avatar);
Cleanup on delete. Add a model observer or deleting event to remove associated files. Because there is no media table, the model is the only record of what exists on disk, so deletion logic must be explicit.
boot/event behavior and add explicit cleanup. A scheduled command that compares disk contents against model paths is a useful safety net.APP_URL.public disk locally, run php artisan storage:link. Missing symlinks are a common “file uploaded but 404” cause.php artisan config:clear during development. Config changes are invisible while cached.UploadedFile, not a string path. Whitelist media attributes in $fillable only if you control the values.composer.json, read the trait before upgrading, and avoid relying on undocumented behavior. If the package stalls, the trait is small enough to fork or replace with a local helper.parent in a way that conflicts.How can I help you explore Laravel packages today?