PHP code example of andydefer / laravel-rattachments
1. Go to this page and download the library: Download andydefer/laravel-rattachments library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
andydefer / laravel-rattachments example snippets
// ❌ Une table par type de relation
// friendships table
// follows table
// blocks table
// tags table
// memberships table
// invitations table
// ...
// ❌ Ou des relations complexes avec des packages lourds
// Spatie\Permission, Laravel\Nova, etc.
// ✅ Une table pour toutes les relations
// Une seule table rattachments gère :
// - Amitiés
// - Abonnements
// - Blocages
// - Tags
// - Adhésions
// - Invitations
// - Et tout ce que vous voulez !
// Une relation est définie par :
// - Qui attache (rattachable)
// - Qui est attaché (target)
// - Le rôle de la relation
// - Des métadonnées optionnelles
$user1->attachTo($user2, FriendRole::FRIEND); // Une amitié
$user1->attachTo($user2, BlockRole::BLOCKED); // Un blocage
$doctor->attachTo($hospital, HospitalRole::DOCTOR); // Une affiliation
$post->attachTo($tag, TagRole::TAG); // Un tag
// Un médecin (rattachable) est rattaché à un hôpital (target)
// avec le rôle "doctor" et des métadonnées sur ses horaires
$attachment = $service->attach(
$doctor, // rattachable
$hospital, // target
HospitalUserRole::DOCTOR, // rôle
[ // métadonnées
'consultation_days' => ['monday', 'wednesday', 'friday'],
'consultation_hours' => '09:00-17:00',
]
);
// ✅ User → Hospital
$user->attachTo($hospital, Role::DOCTOR);
// ✅ Hospital → User
$hospital->attachTo($user, Role::PATIENT);
// ✅ User → User
$user1->attachTo($user2, FriendRole::FRIEND);
// ✅ Post → Tag
$post->attachTo($tag, TagRole::TAG);
// ✅ Doctor → Specialty
$doctor->attachTo($specialty, SpecialtyRole::PRIMARY);
final class User extends Model implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
Hospital::class => [
HospitalUserRole::DOCTOR,
HospitalUserRole::STAFF,
HospitalUserRole::ADMIN,
],
Pharmacy::class => [
PharmacyUserRole::PHARMACIST,
PharmacyUserRole::STAFF,
],
];
}
}
public function uniqueTargets(): array
{
return [
// Un seul Hospital (n'importe quel rôle)
Hospital::class => [],
// Un seul CHIEF par Hospital
Hospital::class => [Role::CHIEF],
// Un seul BEST_FRIEND
User::class => [FriendRole::BEST_FRIEND],
// Une seule PRIMARY specialty
Specialty::class => [Role::PRIMARY],
];
}
// ✅ OK - Premier CHIEF
$doctor->attachTo($hospital1, Role::CHIEF);
// ❌ ERREUR - Déjà CHIEF d'un hôpital
$doctor->attachTo($hospital2, Role::CHIEF);
// "Doctor already has a unique attachment to Hospital with role 'chief'"
// ✅ OK - Peut être DOCTOR dans plusieurs hôpitaux
$doctor->attachTo($hospital2, Role::DOCTOR);
$doctor->attachTo($hospital3, Role::DOCTOR);
public function disallowedTargets(): array
{
return [
Specialty::class => [], // Bloque TOUS les rattachements
Post::class => [PostUserRole::REVIEWER], // Bloque ce rôle
Hospital::class => [HospitalUserRole::ADMIN], // Bloque ADMIN
];
}
// Rôles pour un médecin dans un hôpital
enum HospitalRole: string implements EnumerableInterface
{
case DOCTOR = 'doctor';
case CHIEF = 'chief';
case RESIDENT = 'resident';
public function getValue(): string
{
return $this->value;
}
}
// Rôles pour un utilisateur dans un réseau social
enum FriendRole: string implements EnumerableInterface
{
case FRIEND = 'friend';
case BEST_FRIEND = 'best_friend';
case ACQUAINTANCE = 'acquaintance';
public function getValue(): string
{
return $this->value;
}
}
$role = $attachment->role;
if ($role instanceof UnknownRole) {
Log::warning('Unknown role detected', [
'attachment_id' => $attachment->id,
'role_value' => $role->getValue(),
]);
}
use AndyDefer\LaravelRattachments\Enums\HookPosition;
enum HookPosition: string
{
case RATTACHABLE = 'rattachable';
case TARGET = 'target';
}
class User extends Model implements RattachmentInterface
{
use HasRattachments;
public function beforeAttach(
Model $other,
EnumerableInterface $role,
array $metadata,
HookPosition $position
): void {
Log::info('Before attach', [
'user_id' => $this->id,
'other_type' => get_class($other),
'other_id' => $other->getKey(),
'role' => $role->getValue(),
'position' => $position->value,
]);
// Validation supplémentaire
if ($position === HookPosition::RATTACHABLE && $role->getValue() === 'admin') {
throw new \RuntimeException('Only administrators can attach with admin role');
}
}
public function afterAttach(
Model $other,
EnumerableInterface $role,
Model $attachment,
HookPosition $position
): void {
AuditLog::create([
'user_id' => $this->id,
'action' => 'attach',
'target_type' => get_class($other),
'target_id' => $other->getKey(),
'role' => $role->getValue(),
'attachment_id' => $attachment->id,
'position' => $position->value,
]);
// Invalider le cache
Cache::forget("user_{$this->id}_attachments");
// Envoyer une notification
if ($position === HookPosition::TARGET) {
$other->notify(new UserAttachedToYouNotification($this, $role));
}
}
public function beforeDetach(
Model $other,
Model $attachment,
HookPosition $position
): void {
Log::info('Before detach', [
'user_id' => $this->id,
'attachment_id' => $attachment->id,
]);
}
public function afterDetach(
Model $other,
Model $attachment,
HookPosition $position
): void {
Cache::forget("user_{$this->id}_attachments");
if ($position === HookPosition::RATTACHABLE) {
$other->notify(new UserDetachedFromYouNotification($this));
}
}
}
use AndyDefer\LaravelRattachments\Services\RattachmentService;
class DoctorController extends Controller
{
public function __construct(
private readonly RattachmentService $rattachmentService
) {}
}
// Rattacher plusieurs modèles à une même cible
$attachments = $this->rattachmentService->attachMultiple(
collect([$doctor1, $doctor2, $doctor3]),
$hospital,
HospitalUserRole::DOCTOR,
['department' => 'Cardiology']
);
// Rattacher un modèle à plusieurs cibles
$attachments = $this->rattachmentService->attachToMultiple(
$doctor,
collect([$hospital1, $hospital2, $hospital3]),
HospitalUserRole::DOCTOR
);
// Supprimer un rattachement spécifique
$this->rattachmentService->detach($doctor, $hospital);
// Supprimer plusieurs rattachements
$this->rattachmentService->detachMultiple(
collect([$doctor1, $doctor2]),
$hospital
);
// Supprimer tous les rattachements d'un modèle
$this->rattachmentService->detachAll($doctor);
// Vérifier si un modèle est rattaché à un autre
if ($this->rattachmentService->isAttached($doctor, $hospital)) {
// ...
}
// Vérifier si un rôle existe pour une cible
if ($this->rattachmentService->hasRoleAttached($hospital, HospitalUserRole::DOCTOR)) {
// L'hôpital a au moins un médecin
}
// Récupérer un attachement spécifique
$attachment = $this->rattachmentService->getAttachment($doctor, $hospital);
// Récupérer tous les modèles rattachés à une cible
$doctors = $this->rattachmentService->getRattachables($hospital);
// Récupérer les modèles rattachés avec un rôle spécifique
$doctors = $this->rattachmentService->getRattachablesByRole($hospital, HospitalUserRole::DOCTOR);
// Récupérer les modèles rattachés d'un type spécifique
$doctors = $this->rattachmentService->getRattachablesByType($hospital, Doctor::class);
// Récupérer les modèles rattachés d'un type et rôle spécifiques
$chiefs = $this->rattachmentService->getRattachablesByTypeAndRole($hospital, Doctor::class, HospitalRole::CHIEF);
// Récupérer les modèles rattachés d'un type avec plusieurs rôles
$staff = $this->rattachmentService->getRattachablesByTypeAndRoles($hospital, Doctor::class, [HospitalRole::DOCTOR, HospitalRole::CHIEF]);
// Récupérer les modèles rattachés de plusieurs types avec plusieurs rôles
$personnel = $this->rattachmentService->getRattachablesByTypesAndRoles($hospital, [Doctor::class, Nurse::class], [HospitalRole::DOCTOR, HospitalRole::STAFF]);
// Récupérer toutes les cibles d'un modèle
$hospitals = $this->rattachmentService->getTargets($doctor);
// Récupérer les cibles d'un type spécifique
$hospitals = $this->rattachmentService->getTargetsByType($doctor, Hospital::class);
// Récupérer les cibles par type et rôle
$hospitals = $this->rattachmentService->getTargetsByTypeAndRole(
$doctor,
Hospital::class,
HospitalUserRole::DOCTOR
);
// Récupérer les cibles de plusieurs types avec plusieurs rôles
$targets = $this->rattachmentService->getTargetsByTypesAndRoles(
$doctor,
[Hospital::class, Pharmacy::class],
[HospitalUserRole::DOCTOR, PharmacyUserRole::PHARMACIST]
);
// Paginer les cibles
$hospitals = $this->rattachmentService->getTargetsPaginated($doctor, 10, 2);
// Paginer les cibles par rôle
$hospitals = $this->rattachmentService->getTargetsByRolePaginated(
$doctor,
HospitalUserRole::DOCTOR,
10,
2
);
// Paginer les rattachables
$doctors = $this->rattachmentService->getRattachablesPaginated($hospital, 10, 2);
// Paginer les rattachables par rôle
$doctors = $this->rattachmentService->getRattachablesByRolePaginated(
$hospital,
HospitalUserRole::DOCTOR,
10,
2
);
// Paginer les rattachables par type
$doctors = $this->rattachmentService->getRattachablesByTypePaginated(
$hospital,
Doctor::class,
10,
2
);
// Compter les cibles
$total = $this->rattachmentService->countTargets($doctor);
// Compter les cibles par rôle
$total = $this->rattachmentService->countTargetsByRole($doctor, HospitalUserRole::DOCTOR);
// Compter les rattachables
$total = $this->rattachmentService->countRattachables($hospital);
// Compter les rattachables par rôle
$total = $this->rattachmentService->countRattachablesByRole($hospital, HospitalUserRole::DOCTOR);
// Mettre à jour le rôle
$this->rattachmentService->updateRole($doctor, $hospital, HospitalUserRole::ADMIN);
// Mettre à jour plusieurs rôles
$this->rattachmentService->updateRoleForMultiple(
collect([$doctor1, $doctor2]),
$hospital,
HospitalUserRole::ADMIN
);
// Mettre à jour les métadonnées (remplacement)
$this->rattachmentService->updateMetadata($doctor, $hospital, [
'consultation_hours' => '10:00-18:00',
]);
// Fusionner les métadonnées (conserve les existantes)
$this->rattachmentService->mergeMetadata($doctor, $hospital, [
'availability' => 'Monday-Friday',
]);
// Synchronise tous les rattachements d'un modèle
$attachments = $this->rattachmentService->syncAttachments($doctor, [
[
'target' => $hospital1,
'role' => HospitalUserRole::DOCTOR,
'metadata' => ['primary' => true],
],
[
'target' => $hospital2,
'role' => HospitalUserRole::DOCTOR,
],
[
'target' => $pharmacy,
'role' => PharmacyUserRole::PHARMACIST,
'metadata' => ['supplier' => true],
],
]);
// Les rattachements précédents qui ne sont pas dans la liste sont supprimés
// Rôles distincts pour ce modèle comme rattachable
$roles = $this->rattachmentService->getDistinctRolesForRattachable($doctor);
// Rôles distincts pour ce modèle comme cible
$roles = $this->rattachmentService->getDistinctRolesForTarget($hospital);
// Vérifier si des attachements existent entre deux types
$exists = $this->rattachmentService->hasAttachmentsBetweenTypes(
Doctor::class,
Hospital::class
);
// Récupérer tous les attachements entre deux types
$attachments = $this->rattachmentService->getAttachmentsBetweenTypes(
Doctor::class,
Hospital::class
);
// Supprimer tous les attachements entre deux types
$count = $this->rattachmentService->deleteAllAttachmentsBetweenTypes(
Doctor::class,
Hospital::class
);
class Doctor extends Model implements RattachmentInterface
{
use HasRattachments;
}
$user = User::find(1);
// Récupérer les cibles (ce que User a attaché)
$hospitals = $user->getTargetsByType(Hospital::class);
// Récupérer les rattachables (ce qui est attaché à User)
$specialties = $user->getRattachablesByType(Specialty::class);
// Vérifier les deux sens
if ($user->isAttachedTo($hospital)) {
// User est attaché à Hospital
}
if ($hospital->isAttachedTo($user)) {
// Hospital est attaché à User
}
// Compter les cibles
$totalHospitals = $user->countTargets();
$totalDoctors = $user->countTargetsByRole(HospitalRole::DOCTOR);
// Compter les rattachables
$totalUsers = $hospital->countRattachables();
$totalDoctors = $hospital->countRattachablesByRole(HospitalRole::DOCTOR);
class User extends Model implements RattachmentInterface
{
use HasRattachments;
// Amitié bidirectionnelle
public function becomeFriendWith(User $friend): void
{
$this->attachTo($friend, FriendRole::FRIEND);
$friend->attachTo($this, FriendRole::FRIEND);
}
public function unfriend(User $friend): void
{
$this->detachFrom($friend);
$friend->detachFrom($this);
}
public function getFriends(): Collection
{
return $this->getTargetsByTypeAndRole(User::class, FriendRole::FRIEND);
}
public function getBestFriends(): Collection
{
return $this->getTargetsByTypeAndRole(User::class, FriendRole::BEST_FRIEND);
}
// Suivi unidirectionnel
public function follow(User $user): void
{
$this->attachTo($user, FollowRole::FOLLOWER);
}
public function unfollow(User $user): void
{
$this->detachFrom($user);
}
public function isFollowing(User $user): bool
{
return $this->isAttachedTo($user);
}
public function getFollowers(): Collection
{
return $this->getRattachablesByRole(FollowRole::FOLLOWER);
}
public function getFollowing(): Collection
{
return $this->getTargetsByRole(FollowRole::FOLLOWER);
}
// Blocage
public function block(User $user): void
{
$this->attachTo($user, BlockRole::BLOCKED);
}
public function blockMutually(User $user): void
{
$this->attachTo($user, BlockRole::BLOCKED);
$user->attachTo($this, BlockRole::BLOCKED);
}
public function unblock(User $user): void
{
$this->detachFrom($user);
}
public function isBlocking(User $user): bool
{
return $this->isAttachedTo($user);
}
public function getBlockedUsers(): Collection
{
return $this->getTargetsByRole(BlockRole::BLOCKED);
}
}
class User extends Model implements RattachmentInterface
{
use HasRattachments;
public function becomeFriendWith(User $friend): void
{
$this->attachTo($friend, FriendRole::FRIEND);
$friend->attachTo($this, FriendRole::FRIEND);
}
public function unfriend(User $friend): void
{
$this->detachFrom($friend);
$friend->detachFrom($this);
}
public function getFriends(): Collection
{
return $this->getTargetsByTypeAndRole(User::class, FriendRole::FRIEND);
}
public function getBestFriends(): Collection
{
return $this->getTargetsByTypeAndRole(User::class, FriendRole::BEST_FRIEND);
}
public function isFriendWith(User $user): bool
{
return $this->isAttachedTo($user);
}
}
class User extends Model implements RattachmentInterface
{
use HasRattachments;
public function follow(User $user): void
{
$this->attachTo($user, FollowRole::FOLLOWER);
}
public function unfollow(User $user): void
{
$this->detachFrom($user);
}
public function isFollowing(User $user): bool
{
return $this->isAttachedTo($user);
}
public function getFollowers(): Collection
{
return $this->getRattachablesByRole(FollowRole::FOLLOWER);
}
public function getFollowing(): Collection
{
return $this->getTargetsByRole(FollowRole::FOLLOWER);
}
}
class Post extends Model implements RattachmentInterface
{
use HasRattachments;
public function addTag(Tag $tag, TagRole $role): void
{
$this->attachTo($tag, $role, [
'added_at' => now()->toDateTimeString(),
]);
}
public function removeTag(Tag $tag): void
{
$this->detachFrom($tag);
}
public function getTags(): Collection
{
return $this->getTargetsByType(Tag::class);
}
public function getPrimaryTags(): Collection
{
return $this->getTargetsByTypeAndRole(Tag::class, TagRole::PRIMARY);
}
public function syncTags(array $tags): Collection
{
return $this->syncAttachments($tags);
}
}
class Doctor extends Model implements RattachmentInterface
{
use HasRattachments;
public function workAt(Hospital $hospital, HospitalRole $role): void
{
$this->attachTo($hospital, $role, [
'start_date' => now()->toDateString(),
]);
}
public function leaveHospital(Hospital $hospital): void
{
$this->detachFrom($hospital);
}
public function promoteToChief(Hospital $hospital): void
{
$this->updateRoleFor($hospital, HospitalRole::CHIEF);
}
public function getHospitals(): Collection
{
return $this->getTargetsByType(Hospital::class);
}
public function beforeAttach(
Model $other,
EnumerableInterface $role,
array $metadata,
HookPosition $position
): void {
Log::info('Doctor ' . $this->id . ' is being attached', [
'hospital_id' => $other->getKey(),
'role' => $role->getValue(),
]);
}
public function afterAttach(
Model $other,
EnumerableInterface $role,
Model $attachment,
HookPosition $position
): void {
// Envoyer une notification à l'hôpital
$other->notify(new DoctorJoinedNotification($this, $role));
}
}
$user->attachTo($profile, ProfileRole::USER);
// User est le rattachable, Profile est le target
// "User a un Profile"
// ❌ Erreur
class User extends Model
{
use HasRattachments;
}
// ✅ OK
class User extends Model implements RattachmentInterface
{
use HasRattachments;
}
// ❌ Erreur
$user->attachTo($profile);
// ✅ OK
$user->attachTo($profile, ProfileRole::USER);
public function allowedTargets(): array
{
return [
Profile::class => [ProfileRole::USER, ProfileRole::ADMIN],
];
}
public function disallowedTargets(): array
{
return [
Profile::class => [ProfileRole::ADMIN], // ✅ ADMIN est bloqué
];
}
// ✅ OK
$user->attachTo($profile, ProfileRole::USER);
// ❌ Erreur - ADMIN est bloqué
$user->attachTo($profile, ProfileRole::ADMIN);
// Role "admin" is disallowed
// ❌ Erreur - Circularité
class User implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
Profile::class => [ProfileRole::USER],
];
}
}
class Profile implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
User::class => [ProfileRole::USER], // ❌ Circularité !
];
}
}
// Exception: Circular relationship detected: User → Profile with role "user"
// and Profile → User with the same role.
// ❌ Erreur - Circularité sur uniqueTargets
class User implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
Profile::class => [ProfileRole::USER],
];
}
public function uniqueTargets(): array
{
return [
Profile::class => [ProfileRole::USER], // Un User ne peut avoir qu'un seul Profile
];
}
}
class Profile implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
User::class => [ProfileRole::USER],
];
}
public function uniqueTargets(): array
{
return [
User::class => [ProfileRole::USER], // Un Profile ne peut appartenir qu'à un seul User
];
}
}
// 💥 Exception: Circular unique constraint detected: User → Profile with role "user"
// and Profile → User with the same role.
// This creates a circular dependency. Define the unique constraint in only one direction.
// ❌ Erreur
$user->attachTo($user, FriendRole::FRIEND);
// Cannot attach a model to itself. App\Models\User 1 cannot be attached to itself.
// ✅ Recommandé
class User implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
Profile::class => [ProfileRole::USER],
];
}
}
class Profile implements RattachmentInterface
{
public function allowedTargets(): array
{
return []; // ❌ Ne pas autoriser l'inverse
}
}
class Profile implements RattachmentInterface
{
public function disallowedTargets(): array
{
return [
User::class => [ProfileRole::USER], // ✅ Bloque l'inverse
];
}
}
// ❌ À ÉVITER
class User implements RattachmentInterface
{
public function allowedTargets(): array
{
return [
Profile::class => [ProfileRole::USER], // ✅ Autorisé
];
}
public function disallowedTargets(): array
{
return [
Profile::class => [ProfileRole::USER], // ❌ Mais aussi bloqué !
];
}
}
public function becomeFriendWith(User $friend): void
{
$this->attachTo($friend, FriendRole::FRIEND);
$friend->attachTo($this, FriendRole::FRIEND);
}