andydefer / laravel-images
Laravel package providing polymorphic image management with upload, manipulation, storage, and multiple image types (avatar, cover, gallery, thumbnail, etc.) with Repository pattern, DTOs, Enums, and Value Objects.
Requires
- php: ^8.2
- andydefer/laravel-utils: ^0.5.2
- intervention/image: ^3.0
- laravel/framework: ^12.0|^13.0|^14.0|^15.0
Requires (Dev)
- barryvdh/laravel-ide-helper: ^3.6
- composer/composer: ^2.0
- jetbrains/phpstorm-attributes: ^1.2
- larastan/larastan: ^3.8
- laravel/pint: ^1.26
- orchestra/testbench: ^10.8
- phpunit/phpunit: ^12.5
- rector/rector: *
- symfony/var-dumper: ^7.0
- vimeo/psalm: ^6.14
This package is auto-updated.
Last update: 2026-08-07 18:04:56 UTC
README
Table des matières
- Installation
- Configuration
- Concepts de base
- Gestion des images
- Gestion des albums
- Processeurs d'images
- Stockage
- Directives CLI
- Exemples complets
- API Référence
- Événements automatiques
- Relations inverses (Light/Dark)
1. Installation
composer require andydefer/laravel-images
Prérequis
- PHP 8.1 ou supérieur
- Laravel 10.x, 11.x, 12.x, 13.x, 14.x ou 15.x
- Extension GD (par défaut) ou Imagick
Publier les migrations
php artisan vendor:publish --tag=images-migrations php artisan migrate
Publier la configuration
php artisan vendor:publish --tag=images-config
2. Configuration
Fichier de configuration
// config/images.php return [ // Driver du processeur d'images 'driver' => env('IMAGE_DRIVER', 'gd'), // ou 'imagick' // Disque de stockage 'disk' => env('IMAGE_DISK', 'public'), ];
Variables d'environnement
IMAGE_DRIVER=gd IMAGE_DISK=public
3. Concepts de base
3.1 Image
Une image est un modèle Eloquent qui représente un fichier image stocké sur le disque.
use AndyDefer\LaravelImages\Models\Image; // Propriétés principales $image->id; // UUID unique $image->path; // Chemin relatif (ImagePathVO) $image->filename; // Nom du fichier $image->original_filename; // Nom original $image->extension; // Extension (jpg, png, etc.) $image->mime_type; // Type MIME $image->size; // Taille en bytes $image->type; // Type d'image (avatar, cover, gallery, etc.) $image->metadata; // Métadonnées (ImageMetadataVO) $image->is_primary; // Image principale $image->is_processed; // Traitée ou non $image->order; // Ordre d'affichage $image->inverse_image_id; // UUID de l'image inverse (light/dark)
3.2 Album
Un album regroupe plusieurs images avec un ordre défini.
use AndyDefer\LaravelImages\Models\Album; $album->id; // UUID unique $album->name; // Nom de l'album $album->slug; // Slug unique $album->description; // Description $album->is_public; // Public ou privé (BinaryChoice) $album->is_featured; // Mis en avant (BinaryChoice) $album->images; // Images de l'album $album->coverImage; // Image de couverture
3.3 Relations polymorphiques
Les images et albums utilisent des relations polymorphiques (morphTo, morphToMany) pour s'attacher à n'importe quel modèle Eloquent.
use AndyDefer\LaravelImages\Models\Image; use AndyDefer\LaravelImages\Models\Album; class User extends Model { // Une image s'attache à n'importe quel modèle via imageable() // Un album s'attache à n'importe quel modèle via albumable() }
3.4 Trait HasMediables
Le package fournit un trait HasMediables pour ajouter des attributs calculés à vos modèles.
Installation dans un modèle
<?php namespace App\Models; use AndyDefer\LaravelImages\Traits\HasMediables; use Illuminate\Database\Eloquent\Model; class User extends Model { use HasMediables; // Aucune relation nécessaire ! // Les attributs sont disponibles directement }
Attributs disponibles
| Attribut | Type | Description |
|---|---|---|
has_images |
bool |
Vérifie si le modèle a des images |
images_count |
int |
Nombre total d'images |
primary_image |
`Image | null` |
avatar |
`Image | null` |
cover |
`Image | null` |
banner |
`Image | null` |
logo |
`Image | null` |
icon |
`Image | null` |
gallery_images |
Collection<Image> |
Images de type GALLERY |
has_albums |
bool |
Vérifie si le modèle a des albums |
albums_count |
int |
Nombre total d'albums |
primary_album |
`Album | null` |
featured_album |
`Album | null` |
public_albums |
Collection<Album> |
Albums publics |
private_albums |
Collection<Album> |
Albums privés |
Exemple d'utilisation
$user = User::find(1); // Vérifier si l'utilisateur a des images if ($user->has_images) { echo "L'utilisateur a {$user->images_count} images"; } // Récupérer l'avatar $avatar = $user->avatar; if ($avatar) { echo $avatar->full_url; } // Récupérer les albums publics foreach ($user->public_albums as $album) { echo $album->name; }
4. Gestion des images
4.1 Upload d'une image
use AndyDefer\LaravelImages\Services\ImageService; use AndyDefer\LaravelImages\Enums\ImageType; use AndyDefer\LaravelImages\Records\ImageOptionsRecord; $imageService = app(ImageService::class); // Upload simple $image = $imageService->upload( $request->file('avatar'), $user, // Modèle parent (polymorphique) auth()->user(), // Uploadé par ImageType::AVATAR, new ImageOptionsRecord( alt_text: 'Photo de profil', is_primary: true, generate_thumbnails: true, ) );
4.2 Upload multiple
$images = $imageService->uploadMultiple( $request->file('photos'), $post, auth()->user(), ImageType::GALLERY, new ImageOptionsRecord( generate_thumbnails: true, ) ); foreach ($images as $image) { echo $image->filename . "\n"; }
4.3 Récupération des images
// Toutes les images d'un modèle $images = $imageService->getImagesForModel($post); // Images d'un type spécifique $avatars = $imageService->getImagesForModel($user, ImageType::AVATAR); // Image principale $primary = $imageService->getPrimaryImage($user); // Une image par UUID $image = $imageService->findImage('550e8400-e29b-41d4-a716-446655440000'); // Images mises à jour récemment $recent = $imageService->getImagesUpdatedAfter( DateTimeVO::from(now()->subDays(7)) );
4.4 Mise à jour
use AndyDefer\LaravelImages\Records\ImageRecord; // Mettre à jour les métadonnées $image = $imageService->update( ImageRecord::from(['metadata' => new ImageMetadataVO([ 'alt_text' => 'Nouveau texte alternatif', 'caption' => 'Nouvelle légende', ])]), $imageId ); // Définir comme image principale $imageService->setAsPrimary($imageId, $post); // Réorganiser les images $imageService->reorder(['uuid1', 'uuid2', 'uuid3']);
4.5 Suppression
// Supprimer une image $imageService->delete($imageId, deleteFile: true); // Supprimer plusieurs images $imageService->deleteMultiple(['uuid1', 'uuid2', 'uuid3'], deleteFile: true); // Supprimer toutes les images d'un modèle $imageService->deleteAllForModel($post, deleteFile: true);
4.6 Miniatures
// Récupérer l'URL d'une miniature $small = $imageService->getThumbnailUrl($imageId, 'small'); $medium = $imageService->getThumbnailUrl($imageId, 'medium'); $large = $imageService->getThumbnailUrl($imageId, 'large'); // Dans un template Blade <img src="{{ $imageService->getThumbnailUrl($image->id, 'small') }}" />
5. Gestion des albums
5.1 Création d'un album
use AndyDefer\LaravelImages\Services\AlbumService; use AndyDefer\LaravelCluster\Enums\BinaryChoice; use AndyDefer\LaravelImages\Records\AlbumOptionsRecord; $albumService = app(AlbumService::class); $album = $albumService->createAlbum( $user, 'Mes photos de vacances', new AlbumOptionsRecord( description: 'Photos de mon voyage en Italie', is_public: BinaryChoice::YES, is_featured: BinaryChoice::NO, ) );
5.2 Gestion des images d'un album
// Ajouter des images à un album $albumService->addImagesToAlbum($album, ['uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5']); // Ajouter une image avec une position spécifique $albumService->addImageToAlbum($album, $imageId, $order = 3); // Réorganiser les images $albumService->reorderAlbumImages($album, ['uuid3', 'uuid1', 'uuid4', 'uuid2', 'uuid5']); // Retirer une image $albumService->removeImageFromAlbum($album, $imageId); // Vider l'album $albumService->removeAllImagesFromAlbum($album);
5.3 Récupération des albums
// Albums d'un modèle (publics uniquement) $albums = $albumService->getAlbumsForModel($user, onlyPublic: true); // Tous les albums (publics et privés) $allAlbums = $albumService->getAlbumsForModel($user, onlyPublic: false); // Album par slug $album = $albumService->getAlbumBySlug('mes-photos-de-vacances'); // Albums mis en avant $featured = $albumService->getFeaturedAlbums(10);
5.4 Gestion de la couverture
// Définir la couverture $albumService->setCoverImage($album, $imageId); // Récupérer la couverture $cover = $albumService->getAlbumCoverImage($album);
5.5 Mise à jour et suppression
// Mettre à jour un album $album = $albumService->updateAlbum( $albumId, new AlbumOptionsRecord( name: 'Nouveau nom', description: 'Nouvelle description', is_public: BinaryChoice::NO, is_featured: BinaryChoice::YES, ) ); // Dupliquer un album $duplicate = $albumService->duplicateAlbum($album, 'Copie - Mes photos'); // Supprimer un album $albumService->deleteAlbum($albumId, deleteImages: true);
6. Processeurs d'images
6.1 GD vs Imagick
| Feature | GD | Imagick |
|---|---|---|
| Disponibilité | ✅ Par défaut | ⚠️ Installation requise |
| Performance | Bonne | Excellente |
| Qualité | Bonne | Supérieure |
| Formats supportés | JPG, PNG, GIF, WebP | JPG, PNG, GIF, WebP, HEIC, AVIF, TIFF |
| Utilisation | new GdImageProcessor() |
new ImagickImageProcessor() |
6.2 Utilisation du processeur
use AndyDefer\LaravelImages\Processors\GdImageProcessor; $processor = new GdImageProcessor($storage, $fileSystem); // Redimensionner une image $resized = $processor->resize( $imagePath, // ImagePathVO 800, // Largeur 600, // Hauteur (null = ratio conservé) 85 // Qualité (1-100) ); echo $resized->getFullPath();
6.3 Redimensionnement avancé
// Redimensionner avec ratio conservé $resized = $processor->resize($imagePath, 800); // Redimensionner avec dimensions exactes $resized = $processor->resize($imagePath, 400, 300, 90); // Générer plusieurs tailles $sizes = [ ['width' => 1920, 'height' => 1080, 'quality' => 90], ['width' => 800, 'height' => 600, 'quality' => 85], ['width' => 150, 'height' => 150, 'quality' => 75], ]; foreach ($sizes as $config) { $processor->resize($imagePath, $config['width'], $config['height'], $config['quality']); }
7. Stockage
7.1 Configuration du stockage
use AndyDefer\LaravelImages\Storage\LocalImageStorage; use AndyDefer\PhpServices\Services\FileSystemService; // Création du stockage $fileSystem = new FileSystemService(); $storage = new LocalImageStorage($fileSystem);
Note : Le stockage utilise des chemins relatifs au dossier courant d'exécution. Aucun préfixe n'est ajouté automatiquement.
7.2 Opérations de stockage
// Stocker un fichier $path = $storage->store($uploadedFile, 'users/123/avatars', 'profile.jpg'); // Vérifier l'existence if ($storage->exists($path)) { // Le fichier existe } // Obtenir le chemin complet $fullPath = $storage->getFullPath($path); // Lister les fichiers d'un dossier $files = $storage->files('users/123/avatars'); // Supprimer un fichier $storage->delete($path); // Supprimer plusieurs fichiers $storage->deleteMultiple($files);
8. Directives CLI
8.1 Installation de la CLI
Le package fournit une directive CLI pour la gestion des images :
| Directive | Alias | Description |
|---|---|---|
images:scan |
ims |
Scanne un dossier et génère un inventaire JSON ou PHP |
8.2 Commande de scan
./bin/images images:scan {source} {output} {depth=0} {extensions*} {excludes*} {--options}
Paramètres :
| Paramètre | Description |
|---|---|
{source} |
Dossier source à scanner |
{output} |
Fichier de sortie (.json ou .php) |
{depth=0} |
Profondeur maximale de scan (0 = illimitée) |
{extensions*} |
Extensions d'images à inclure (ex: png jpg webp ou [png,jpg]) |
{excludes*} |
Dossiers à exclure du scan |
{--hash} |
Inclut le hash MD5 de chaque image |
{--relative} |
Rend les chemins relatifs au répertoire source |
Exemples :
# Scan simple avec sortie JSON ./bin/images images:scan storage/app/public/images scan-result.json # Scan avec profondeur limitée ./bin/images images:scan storage/app/public/images scan-depth.json 1 # Scan avec filtrage d'extensions ./bin/images images:scan storage/app/public/images scan-ext.json 0 [png,jpg] # Scan avec exclusion de dossiers ./bin/images images:scan storage/app/public/images scan-exclude.json 0 [] [compressed,thumbnails] # Scan avec génération de hash MD5 ./bin/images images:scan storage/app/public/images scan-hash.json --hash # Scan avec chemins relatifs ./bin/images images:scan storage/app/public/images scan-relative.json --relative # Sortie PHP array ./bin/images images:scan storage/app/public/images scan-result.php # Utilisation de l'alias ./bin/images ims storage/app/public/images scan-result.json # Combinaison de toutes les options ./bin/images images:scan storage/app/public/images scan-all.json 1 [png,jpg] [compressed,thumbnails] --hash --relative
Exemple de sortie JSON (sans --relative) :
[
{
"path": "storage/app/public/images/avatars/user1.png",
"filename": "user1.png",
"original_filename": "user1.png",
"extension": "png",
"mime_type": "image/png",
"size": 12345,
"width": 800,
"height": 600,
"hash": "5d41402abc4b2a76b9719d911017c592"
}
]
Exemple de sortie JSON (avec --relative) :
[
{
"path": "avatars/user1.png",
"filename": "user1.png",
"original_filename": "user1.png",
"extension": "png",
"mime_type": "image/png",
"size": 12345,
"width": 800,
"height": 600,
"hash": "5d41402abc4b2a76b9719d911017c592"
}
]
Exemple de sortie PHP :
<?php return [ [ 'path' => 'avatars/user1.png', 'filename' => 'user1.png', 'original_filename' => 'user1.png', 'extension' => 'png', 'mime_type' => 'image/png', 'size' => 12345, 'width' => 800, 'height' => 600, 'hash' => '5d41402abc4b2a76b9719d911017c592', ], ];
8.3 Exemple de sortie du scan
$ ./bin/images images:scan storage/app/public/images scan-result.json 2 [png,jpg] [compressed,thumbnails] --hash --relative 🔍 Scanning images... ✅ Source directory: storage/app/public/images 📁 Scanning: storage/app/public/images 📊 Found: 42 images 💾 Output saved to: scan-result.json ✅ Scan completed
9. Exemples complets
9.1 Upload d'avatar avec options
<?php namespace App\Http\Controllers; use AndyDefer\LaravelImages\Services\ImageService; use AndyDefer\LaravelImages\Enums\ImageType; use AndyDefer\LaravelImages\Records\ImageOptionsRecord; use Illuminate\Http\Request; class AvatarController extends Controller { public function __construct( private readonly ImageService $imageService, ) {} public function upload(Request $request) { $request->validate([ 'avatar' => 'required|image|max:2048', ]); $image = $this->imageService->upload( $request->file('avatar'), $request->user(), $request->user(), ImageType::AVATAR, new ImageOptionsRecord( alt_text: 'Avatar de ' . $request->user()->name, is_primary: true, generate_thumbnails: true, order: 1, ) ); return response()->json([ 'message' => 'Avatar uploadé avec succès', 'image' => [ 'id' => $image->id, 'url' => $image->full_url, 'thumbnail' => $this->imageService->getThumbnailUrl($image->id, 'small'), ], ]); } }
9.2 Galerie d'images
<?php namespace App\Http\Controllers; use AndyDefer\LaravelImages\Services\ImageService; use AndyDefer\LaravelImages\Services\AlbumService; use AndyDefer\LaravelCluster\Enums\BinaryChoice; use AndyDefer\LaravelImages\Records\AlbumOptionsRecord; use Illuminate\Http\Request; class GalleryController extends Controller { public function __construct( private readonly ImageService $imageService, private readonly AlbumService $albumService, ) {} public function createAlbum(Request $request) { $album = $this->albumService->createAlbum( $request->user(), $request->input('name'), new AlbumOptionsRecord( description: $request->input('description'), is_public: BinaryChoice::YES, ) ); // Upload des images $images = $this->imageService->uploadMultiple( $request->file('photos'), $request->user(), $request->user(), ImageType::GALLERY, new ImageOptionsRecord(generate_thumbnails: true) ); // Ajout à l'album $imageIds = $images->pluck('id')->toArray(); $this->albumService->addImagesToAlbum($album, $imageIds); // Définir la couverture if (!empty($imageIds)) { $this->albumService->setCoverImage($album, $imageIds[0]); } return response()->json([ 'message' => 'Album créé avec succès', 'album' => $album, 'images' => $images, ]); } }
9.3 Export d'images avec filtrage
<?php namespace App\Services; use AndyDefer\LaravelImages\Services\ImageService; use Illuminate\Support\Collection; class ImageExportService { public function __construct( private readonly ImageService $imageService, ) {} public function export(Model $model, array $types = null): array { $images = $this->imageService->getImagesForModel($model); if ($types) { $images = $images->filter(fn($img) => in_array($img->type, $types)); } return $images->map(function ($image) { return [ 'id' => $image->id, 'filename' => $image->filename, 'original_filename' => $image->original_filename, 'url' => $image->full_url, 'thumbnails' => [ 'small' => $this->imageService->getThumbnailUrl($image->id, 'small'), 'medium' => $this->imageService->getThumbnailUrl($image->id, 'medium'), 'large' => $this->imageService->getThumbnailUrl($image->id, 'large'), ], 'metadata' => $image->metadata?->toArray(), 'type' => $image->type->value, 'order' => $image->order, 'is_primary' => $image->is_primary, ]; })->toArray(); } }
9.4 Audit d'images avec scan CLI
# 1. Générer un inventaire complet des images ./bin/images images:scan storage/app/public/images scan-result.json # 2. Générer un inventaire avec hash MD5 ./bin/images images:scan storage/app/public/images scan-hash.json --hash # 3. Générer un inventaire avec chemins relatifs ./bin/images images:scan storage/app/public/images scan-relative.json --relative # 4. Générer un inventaire PHP pour traitement programmatique ./bin/images images:scan storage/app/public/images scan-result.php # 5. Analyser uniquement les JPEG/PNG en excluant les dossiers compressés ./bin/images images:scan storage/app/public/images scan-filtered.json 0 [png,jpg] [compressed,thumbnails] --hash --relative
10. API Référence
ImageService
| Méthode | Description |
|---|---|
findImage(string $id): ?Image |
Trouve une image par UUID |
upload(...): Image |
Upload une image |
uploadMultiple(...): Collection |
Upload plusieurs images |
update(ImageRecord $record, string $id): Image |
Met à jour une image |
delete(string $id, bool $deleteFile): void |
Supprime une image |
deleteMultiple(array<string> $ids, bool $deleteFile): void |
Supprime plusieurs images |
deleteAllForModel(Model $model, bool $deleteFile): void |
Supprime toutes les images d'un modèle |
getImagesForModel(Model $model, ?ImageType $type): Collection |
Récupère les images d'un modèle |
getPrimaryImage(Model $model): ?Image |
Récupère l'image principale |
setAsPrimary(string $id, Model $model): void |
Définit une image comme principale |
countImages(Model $model, ?ImageType $type): int |
Compte les images |
getImagesUpdatedAfter(DateTimeVO $date): Collection |
Images mises à jour après une date |
reorder(array<string> $ids): void |
Réorganise les images |
getThumbnailUrl(string $imageId, string $size): string |
URL de la miniature |
syncInverseRelation(Image $image): void |
Synchronise la relation inverse (light/dark) |
getImageProcessor(): ImageProcessorInterface |
Retourne le processeur |
getStorage(): ImageStorageInterface |
Retourne le stockage |
AlbumService
| Méthode | Description |
|---|---|
createAlbum(...): Album |
Crée un album |
addImagesToAlbum(Album $album, array<string> $imageIds): void |
Ajoute des images |
addImageToAlbum(Album $album, string $imageId, int $order): void |
Ajoute une image |
removeImageFromAlbum(Album $album, string $imageId): void |
Retire une image |
removeAllImagesFromAlbum(Album $album): void |
Vide un album |
setCoverImage(Album $album, string $imageId): void |
Définit la couverture |
getAlbumImages(Album $album): Collection |
Images d'un album |
getAlbumsForModel(Model $model, bool $onlyPublic): Collection |
Albums d'un modèle |
| `getAlbumBySlug(string | SlugVO $slug): ?Album` |
updateAlbum(string $id, AlbumOptionsRecord $options): Album |
Met à jour un album |
deleteAlbum(string $id, bool $deleteImages): void |
Supprime un album |
reorderAlbumImages(Album $album, array<string> $imageIds): void |
Réorganise les images |
duplicateAlbum(Album $album, string $newName): Album |
Duplique un album |
countAlbumImages(Album $album): int |
Compte les images |
isAlbumEmpty(Album $album): bool |
Vérifie si l'album est vide |
getAlbumCoverImage(Album $album): ?Image |
Image de couverture |
getFeaturedAlbums(int $limit): Collection |
Albums mis en avant |
ImageType
| Type | Description |
|---|---|
AVATAR |
Photo de profil |
COVER |
Photo de couverture |
GALLERY |
Galerie d'images |
THUMBNAIL |
Miniature |
ATTACHMENT |
Pièce jointe |
LOGO |
Logo |
ICON |
Icône |
BANNER |
Bannière |
PRODUCT |
Image de produit |
BinaryChoice
| Valeur | Description |
|---|---|
YES |
Oui |
NO |
Non |
11. Événements automatiques
Le package utilise des observers Eloquent pour automatiser certaines opérations critiques sans intervention manuelle.
11.1 ImageObserver
L'ImageObserver gère automatiquement les relations inverses entre les variantes d'images (claires/sombres) et maintient l'intégrité référentielle.
| Événement | Action automatique | Description |
|---|---|---|
created |
syncInverseRelation() |
Lorsqu'une nouvelle image est créée, le système détecte automatiquement si c'est une variante -light ou -dark et la lie à son homologue si elle existe dans le même contexte (même modèle parent, même type). |
updated |
syncInverseRelation() |
Lorsque le nom de fichier d'une image est modifié, la relation inverse est réévaluée pour maintenir la cohérence. |
deleted |
inverse_image_id = null |
Lorsqu'une image est supprimée, toutes les images qui la référencent comme inverse_image_id voient cette référence automatiquement mise à null pour éviter les orphelins. |
Exemple concret :
// Upload de deux images variantes $darkFile = UploadedFile::fake()->image('banner-dark.jpg'); $lightFile = UploadedFile::fake()->image('banner-light.jpg'); $darkImage = $imageService->upload($darkFile, $page, null, ImageType::BANNER); $lightImage = $imageService->upload($lightFile, $page, null, ImageType::BANNER); // L'ImageObserver a automatiquement lié les deux images $darkImage->refresh(); $lightImage->refresh(); // Les relations inverses sont automatiquement synchronisées echo $darkImage->inverse_image_id; // UUID de l'image light echo $lightImage->inverse_image_id; // UUID de l'image dark
11.2 AlbumObserver
L'AlbumObserver maintient l'intégrité des relations entre albums et images.
| Événement | Action automatique | Description |
|---|---|---|
deleting |
images()->detach() |
Avant la suppression (soft delete) d'un album, toutes les relations avec ses images sont automatiquement détachées pour éviter les enregistrements orphelins dans la table pivot. |
forceDeleted |
images()->detach() |
Lors de la suppression définitive d'un album, les relations sont également nettoyées pour maintenir la cohérence. |
Exemple concret :
// Création d'un album avec des images $album = $albumService->createAlbum($user, 'Mes photos'); $albumService->addImagesToAlbum($album, ['uuid1', 'uuid2', 'uuid3']); // Suppression de l'album $album->delete(); // L'AlbumObserver a automatiquement détaché toutes les images // Les images 1, 2, 3 existent toujours mais ne sont plus liées à l'album
12. Relations inverses (Light/Dark)
12.1 Principe
Le package supporte nativement la gestion des paires d'images inverses (light/dark) grâce à la colonne inverse_image_id dans la table images. Cette fonctionnalité est particulièrement utile pour :
- Bannières thématiques : Afficher une version claire ou sombre selon le thème de l'interface
- Logos adaptatifs : Proposer un logo clair sur fond sombre et vice-versa
- Icônes contextuelles : Alterner entre variantes selon le contexte d'affichage
12.2 Comment ça fonctionne
La détection des paires se base sur la convention de nommage des fichiers :
| Variante | Pattern | Exemple |
|---|---|---|
| Dark | *-dark.* |
logo-dark.png, banner-dark.jpg |
| Light | *-light.* |
logo-light.png, banner-light.jpg |
12.3 Synchronisation automatique
La synchronisation est entièrement automatique via l'ImageObserver :
-
Création : Lorsque vous uploadez une image avec un nom contenant
-lightou-dark, le système recherche automatiquement son homologue dans le même contexte (mêmeimageable_type,imageable_idettype). -
Liaison : Si l'homologue existe, les deux images sont liées bidirectionnellement via
inverse_image_id. -
Nettoyage : Si l'homologue est supprimé, la référence est automatiquement mise à
null.
12.4 Utilisation
// Upload des deux variantes (l'ordre n'a pas d'importance) $darkFile = UploadedFile::fake()->image('hero-dark.jpg'); $lightFile = UploadedFile::fake()->image('hero-light.jpg'); // L'Observer lie automatiquement les deux images $darkImage = $imageService->upload($darkFile, $page, null, ImageType::BANNER); $lightImage = $imageService->upload($lightFile, $page, null, ImageType::BANNER); // La liaison est automatique $darkImage->refresh(); echo $darkImage->inverse_image_id; // UUID de l'image light // Récupérer l'image inverse via la relation Eloquent $inverse = $darkImage->inverseImage; echo $inverse->filename; // 'hero-light.jpg'
12.5 Synchronisation manuelle
Bien que la synchronisation soit automatique, vous pouvez également la déclencher manuellement :
use AndyDefer\LaravelImages\Models\Image; $image = Image::find('550e8400-e29b-41d4-a716-446655440000'); $imageService->syncInverseRelation($image);
12.6 Cas d'usage avancé
// Récupérer la bonne variante selon le thème de l'utilisateur function getThemeImage(Image $image, string $theme): ?Image { if ($theme === 'dark' && $image->original_filename) { // Si l'image actuelle est light, chercher sa version dark return $image->inverseImage ?? $image; } if ($theme === 'light' && $image->original_filename) { // Si l'image actuelle est dark, chercher sa version light return $image->inverseImage ?? $image; } return $image; } // Dans un template Blade @php $banner = $page->getPrimaryImage(); $displayedBanner = getThemeImage($banner, auth()->user()->theme); @endphp <img src="{{ $displayedBanner->full_url }}" alt="Bannière" />
12.7 Limitations
- La détection se base uniquement sur le nom de fichier original (
original_filename) - Les deux images doivent être dans le même contexte (même
imageable_type,imageable_id,type) - La relation est stockée via une colonne
inverse_image_idde type UUID dans la tableimages