aboleon / metaframework-mediaclass
Mediaclass media management components for MetaFramework
Installs: 13
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/aboleon/metaframework-mediaclass
Requires
- php: ^8.3
- aboleon/metaframework-inputable: ^1.0
- aboleon/metaframework-support: ^1.0
- css-crush/css-crush: ^v5.0.0
- illuminate/database: ^11.0|^12.0
- illuminate/filesystem: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- intervention/image: ^3.11
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0|^12.0
README
Media management components for Laravel applications. This package provides upload UI, database persistence, image resizing, optional cropping, and helpers to retrieve and render media for Eloquent models.
Quick Start
// Get image URL $url = $post->img('cover')->url(); // Get img tag {!! $post->img('cover')->class('rounded')->lazy()->img() !!} // In Blade <x-mfw-media :src="$post->img('cover')" class="rounded" lazy />
Installation
composer require aboleon/metaframework-mediaclass
Publish assets and run migrations:
php artisan vendor:publish --tag=mfw-mediaclass-config php artisan vendor:publish --tag=mfw-mediaclass-assets php artisan vendor:publish --tag=mfw-mediaclass-migrations php artisan vendor:publish --tag=mfw-mediaclass-lang // Views for customization, if needed php artisan vendor:publish --tag=mfw-mediaclass-views php artisan migrate
Model Setup
use Illuminate\Database\Eloquent\Model; use MetaFramework\Mediaclass\Contracts\MediaclassInterface; use MetaFramework\Mediaclass\Concerns\Mediaclass as MediaclassTrait; class Post extends Model implements MediaclassInterface { use MediaclassTrait; public function mediaclassSettings(): array { return [ 'cover' => [ 'label' => 'Cover', 'width' => 1600, 'height' => 900, 'cropable' => true, ], 'gallery' => [ 'label' => 'Gallery', 'width' => 1200, 'height' => 800, ], ]; } }
Two Groups Example (Cover + Gallery)
Use group keys in mediaclassSettings() to define the required dimensions per group:
public function mediaclassSettings(): array { return [ 'cover' => [ 'label' => 'Cover', 'width' => 1600, 'height' => 900, 'cropable' => true, // single crop using the group dimensions ], 'gallery' => [ 'label' => 'Gallery', 'width' => 1200, 'height' => 800, // 'cropable' => ['thumb' => [400, 300]] // optional extra crops ], ]; }
If no group is defined, the package falls back to the default sizes defined in
config/mfw-mediaclass.php under dimensions.
Group-Specific Sizes
You can define multiple sizes for a single group using a sizes array. These
sizes will be used for resizing and for size keys when calling url('key'):
public function mediaclassSettings(): array { return [ 'cover' => [ 'label' => 'Cover', 'sizes' => [ 'xl' => ['width' => 1600, 'height' => 900], 'sm' => ['width' => 1200, 'height' => 500], ], 'cropable' => true, // uses the largest size as the crop target ], ]; }
If sizes is not provided for a group, the package uses the single width /
height pair for that group, or falls back to the global dimensions defaults.
Note: Upload processing relies on Intervention Image. If Intervention Image is not installed, upload tests that hit the upload controller will be skipped.
Displaying Images
Fluent API (Recommended)
The simplest way to display images from your models:
// Get URL $url = $post->img('cover')->url(); $url = $post->img('cover')->url('lg'); // specific size // Get img tag $html = $post->img('cover')->img(); $html = $post->img('cover') ->class('rounded-lg shadow') ->alt('Product photo') ->lazy() ->img(); // Get cropped version $url = $post->img('cover')->crop('banner')->url(); // Check if media exists if ($post->img('cover')->exists()) { // ... } // Multiple images foreach ($post->imgs('gallery') as $img) { echo $img->url(); }
Available Methods
| Method | Description |
|---|---|
->url(?string $size) |
Get URL (default: 'sm') |
->img(?string $size) |
Get <img> tag |
->picture(?array $breakpoints) |
Get <picture> element |
->background() |
Get CSS background-image style |
->urls() |
Get all available URLs as array |
Size Methods:
->size('lg') // Set size ->sm() / ->md() / ->lg() / ->xl() // Shorthand
Crop Methods:
->crop('banner') // Use specific crop ->hasCrop('banner') // Check if crop exists
HTML Attributes:
->class('rounded') // CSS classes ->addClass('shadow') // Add to existing classes ->alt('Description') // Alt text ->id('hero-image') // ID attribute ->lazy() // loading="lazy" ->eager() // loading="eager" ->width(800) // Width attribute ->height(600) // Height attribute ->attr('data-id', 1) // Any attribute ->attrs(['class' => 'rounded', 'id' => 'img']) ->data('gallery', 'main') // data-* attributes
Fallback:
->default('/img/fallback.png') // Custom fallback URL ->noDefault() // No fallback image
Blade Component
{{-- Basic usage --}} <x-mfw-media :src="$post->img('cover')" /> {{-- With attributes --}} <x-mfw-media :src="$post->img('cover')" size="lg" class="rounded-lg" alt="Product image" lazy /> {{-- From model directly --}} <x-mfw-media :model="$post" group="cover" size="lg" /> {{-- As URL only --}} <x-mfw-media :src="$post->img('cover')" type="url" /> {{-- As picture element --}} <x-mfw-media :src="$post->img('cover')" type="picture" /> {{-- With specific crop --}} <x-mfw-media :src="$post->img('cover')" crop="banner" />
Component Attributes:
| Attribute | Type | Description |
|---|---|---|
src |
MediaBuilder | From $model->img('group') |
model |
object | Model instance (with group) |
group |
string | Media group name |
subgroup |
string | Media subgroup filter |
size |
string | Image size (sm, md, lg, xl) |
type |
string | Output: img, url, picture, background |
class |
string | CSS classes |
alt |
string | Alt text |
id |
string | HTML id |
lazy |
bool | Enable lazy loading |
crop |
string | Specific crop key |
default |
string | Fallback URL |
noDefault |
bool | Disable fallback |
data |
array | Data attributes |
breakpoints |
array | For picture element |
Direct Media Model Usage
// If you have a Media model directly $media = Media::find(1); $url = $media->url('lg'); $url = $media->crop('banner'); $html = $media->img('md', ['class' => 'rounded']); // Fluent builder $html = $media->builder() ->class('rounded') ->lazy() ->img();
Uploading Media
Upload Component
<x-mediaclass::uploadable :model="$post" group="cover" :limit="1" />
With options:
<x-mediaclass::uploadable :model="$post" group="gallery" :limit="10" :positions="true" :description="true" maxfilesize="5MB" :cropable="['thumb' => [400, 300]]" />
Stored Media Display (Admin)
<x-mediaclass::stored :model="$post" group="gallery" />
Processing After Save
$post = Post::create($payload); $post->processMedia();
Configuration
Published to config/mfw-mediaclass.php:
return [ 'disk' => 'public', 'dimensions' => [ 'xl' => ['width' => 1920, 'height' => 1080], 'lg' => ['width' => 1400, 'height' => 788], 'md' => ['width' => 700, 'height' => 394], 'sm' => ['width' => 400, 'height' => 225], ], ];
Cropping
Define cropable settings in your model:
public function mediaclassSettings(): array { return [ 'cover' => [ 'width' => 1600, 'height' => 900, 'cropable' => true, // Single crop using group dimensions ], 'banner' => [ 'width' => 1920, 'height' => 400, 'cropable' => [ 'desktop' => [1920, 400], 'mobile' => [800, 400], ], ], ]; }
Access cropped versions:
// Single crop $url = $post->img('cover')->crop('cover')->url(); // Multiple crops $desktop = $post->img('banner')->crop('desktop')->url(); $mobile = $post->img('banner')->crop('mobile')->url(); // Check if crop exists if ($post->img('cover')->hasCrop('cover')) { // ... }
Ghost Media
For media not attached to a specific model instance:
<x-mediaclass::uploadable :model="$post" group="cover" ghost />
Retrieve ghost media:
use MetaFramework\Mediaclass\Mediaclass; $url = Mediaclass::ghostUrl(Post::class, 'cover', 'sm', '/fallback.png');
Legacy API
The original Parser/Printer classes are still available for backward compatibility:
use MetaFramework\Mediaclass\Mediaclass; use MetaFramework\Mediaclass\Printer; // Fetch and parse $parser = (new Mediaclass())->forModel($post, 'cover')->first(); $url = $parser->url; // Render with Printer $html = (new Printer($parser)) ->setClass('rounded') ->setLoading('lazy') ->img('md');
Storage Layout
- Regular:
{model}/{id}/{width}_{filename}.{ext} - Ghost:
{model}/{width}_{filename}.{ext} - Crops:
{model}/{id}/cropped_{key}_{filename}.{ext}
Routes
POST /mediaclass-ajax- Upload/delete/crop actionsGET /mediaclass/cropable/{media}- Crop UI
Testing
composer install
composer test
Requirements
- PHP 8.3+
- Laravel 11+
- Intervention Image