pindab0ter/constrained-morph-to-for-laravel
Installation:
composer require pindab0ter/constrained-morph-to-for-laravel
No publisher or service provider required—just use the trait directly.
First Use Case: Define a constrained polymorphic relationship in a model:
use Pindab0ter\ConstrainedMorphTo\HasConstrainedMorphTo;
class Comment extends Model
{
use HasConstrainedMorphTo;
public function commentable(): HasConstrainedMorphTo
{
return $this->constrainedMorphTo(
'commentable',
[Post::class, User::class] // Only allow Post or User
);
}
}
Key Files to Review:
src/HasConstrainedMorphTo.php (Core trait)src/ConstrainedMorphTo.php (Relationship definition)tests/ (Usage examples and edge cases)Define Constraints:
// Allow only specific models
$this->constrainedMorphTo('polymorphic_name', [ModelA::class, ModelB::class]);
Accessing Relationships:
$comment = Comment::find(1);
$commentable = $comment->commentable; // Returns Post or User, or null if invalid
Dynamic Constraints:
// Use a closure for dynamic constraints (e.g., based on user role)
$this->constrainedMorphTo('polymorphic_name', function () {
return auth()->user()->canAccess() ? [Post::class, Video::class] : [Post::class];
});
morphs table columns (polymorphic_type, polymorphic_id) exist.morphMap in AppServiceProvider if you need custom type aliases:
public function boot()
{
$this->app['router']->morphMap = [
'post' => Post::class,
'user' => User::class,
];
}
return CommentResource::make($comment)->additional([
'commentable' => $comment->commentable?->toArray()
]);
// Only allow 'Post' or 'Video' as commentable
$this->constrainedMorphTo('commentable', [Post::class, Video::class]);
// Admin users can attach to more types
$constraints = auth()->user()->isAdmin()
? [Post::class, User::class, Media::class]
: [Post::class, User::class];
$this->constrainedMorphTo('attachable', $constraints);
// Set a default model if the constraint fails
$this->constrainedMorphTo('fallback', [Post::class], Post::class);
Runtime Errors:
try-catch:
try {
$comment->commentable()->associate($invalidModel);
$comment->save(); // Throws Pindab0ter\ConstrainedMorphTo\Exceptions\InvalidMorphType
} catch (\Exception $e) {
// Handle error (e.g., log or redirect)
}
Caching Issues:
$constraints = Cache::remember("user_{$user->id}_constraints", now()->addHours(1), function () use ($user) {
return $user->getAllowedModels();
});
Morph Map Conflicts:
morphMap, ensure your constrained models are included in the map.getMorphClass() in constrained models if needed:
class Post extends Model
{
public function getMorphClass()
{
return 'post'; // Custom alias
}
}
$this->constrainedMorphTo('polymorphic_name', [Post::class], function () {
\Log::debug('Constraints:', [Post::class, User::class]);
return [Post::class, User::class];
});
dd($model->commentable) to inspect the loaded relationship and verify its type.Custom Exceptions:
use Pindab0ter\ConstrainedMorphTo\Exceptions\InvalidMorphType;
class CustomInvalidMorphException extends InvalidMorphType
{
// Add custom logic
}
use HasConstrainedMorphTo {
HasConstrainedMorphTo::save as traitSave;
}
public function save(array $options = [])
{
try {
$this->traitSave($options);
} catch (InvalidMorphType $e) {
throw new CustomInvalidMorphException($e->getMessage());
}
}
Add Metadata:
protected $constraintMetadata = [
'commentable' => ['allowed' => [Post::class, User::class], 'last_validated' => null],
];
boot():
public static function boot()
{
parent::boot();
static::saved(function ($model) {
foreach ($model->constraintMetadata as $relation => $meta) {
if (!$model->{$relation} instanceof $meta['allowed']) {
// Handle violation
}
}
});
}
Performance:
// In a service or repository
public function getConstraintsFor($model)
{
return Cache::remember("constraints_{$model->id}", now()->addMinutes(60), function () use ($model) {
return $model->getAllowedConstraints();
});
}
How can I help you explore Laravel packages today?