composer require novius/laravel-linkable
php artisan vendor:publish --provider="Novius\LaravelLinkable\LaravelLinkableServiceProvider" --tag=config
php artisan vendor:publish --provider="Novius\LaravelLinkable\LaravelLinkableServiceProvider" --tag=lang
Post):
use Novius\LaravelLinkable\Traits\Linkable;
class Post extends Model {
use Linkable;
public function linkableConfig(): LinkableConfig {
return new LinkableConfig(
routeName: 'post.show',
routeParameterName: 'post',
optionLabel: 'title',
optionGroup: 'Content'
);
}
}
routes/web.php:
Route::get('/posts/{post}', [PostController::class, 'show'])->name('post.show');
url() method in your code:
$post = Post::first();
echo $post->url(); // Generates `/posts/1`
// In a Nova resource or controller:
$post = Post::find(1);
$post->linkable()->attach(Category::find(5)); // Links post to category
Now, $post->url() will include the category context if configured.
$model->linkable()->attach($linkedModel); // One-to-one or many
$model->linkable()->detach($linkedModel);
$linkedModels = $model->linkable()->get(); // All linked models
$linkedModels = $model->linkable()->where('category_id', 1)->get(); // Filtered
public function linkableConfig(): LinkableConfig {
return new LinkableConfig(
getUrlCallback: fn (Model $model) => route('post.details', ['post' => $model, 'tab' => 'comments'])
);
}
spatie/laravel-localization):
$post->url('fr'); // Generates `/fr/posts/1`
Linkable::make('Related Post')
->optionsClasses([Post::class, Category::class])
->rules(['required', 'exists:posts,id']);
Linkable::make('Featured Image')
->setLinkableClasses([Media::class])
->setLocale(request()->locale);
public function linkableConfig(): LinkableConfig {
return new LinkableConfig(
optionsQuery: fn (Builder $query) => $query->where('published', true)
);
}
$model = Linkable::resolve($linkableId); // Resolves via `resolveQuery`
resolveQuery to scope by tenant:
resolveQuery: fn (Builder $query) => $query->where('tenant_id', auth()->tenant()->id),
optionsQuery includes withTrashed() if needed:
optionsQuery: fn (Builder $query) => $query->withTrashed()->where('active', true),
linkableConfig() if models are static:
protected static function boot() {
parent::boot();
static::addGlobalScope('cacheConfig', fn (Builder $builder) => $builder->cachedConfig());
}
Linkable facade for unit tests:
$this->mock(Linkable::class)->shouldReceive('getLink')->andReturn('/mocked-url');
Route Name Mismatches:
routeName/routeParameterName in linkableConfig() must match your route definitions exactly.php artisan route:list and use route('route.name') to test.Locale Conflicts:
disable_localization: true in config but getUrlCallback uses $model->locale, URLs may break.public function getLocale() {
return $this->locale ?? app()->getLocale();
}
Circular References:
ModelA to ModelB and vice versa can cause infinite loops in queries.depth parameter to queries:
$model->linkable()->whereDepth('<', 3)->get();
Nova Field Performance:
Linkable::make() cause slow admin panel loads.optionsQuery:
Linkable::make('Author')
->optionsQuery(fn (Builder $query) => $query->where('is_active', true)->limit(100))
AGPL License:
URL Generation Failures:
Linkable::getLink($model) directly to isolate issues:
dd(Linkable::getLink($model)); // Debug raw output
routeName exists:
php artisan route:list | grep 'post.show'
Linkable Field Not Showing Options:
optionLabel is a valid column or closure:
optionLabel: fn (Model $model) => "{$model->title} (#{$model->id})",
optionGroup is unique across models.Preview Token Issues:
previewUrl() fails, ensure:
preview_token field exists in the database.guard_preview config matches your auth guard.Custom Link Types: Add support for non-model links (e.g., external URLs):
// In config/linkable.php
'linkable_routes' => [
'external' => 'https://example.com/{id}',
];
Then use in Nova:
Linkable::make('External Link')->optionsClasses(['external']),
Validation Rules: Extend the trait to add validation:
use Illuminate\Validation\Rule;
public function linkableConfig(): LinkableConfig {
return new LinkableConfig(
validateCallback: fn (Model $model, $linkedModel) => [
Rule::unique('post_linkables', 'linked_id')
->where('linkable_id', $model->id)
->ignore($model->linkable()->getKey()),
]
);
}
GraphQL Integration: Create a custom resolver for Laravel GraphQL:
yield GraphQL::type('Linkable')
->resolve(fn ($root, $args) => Linkable::getLink($root->link));
Bulk Operations:
Add a syncLinkable() method to your model:
public function syncLinkable(array $ids) {
$this->linkable()->sync($ids);
}
Autoloading Models:
autoload_models_in config scans for models using the Linkable trait. If a model is missed:
linkable_models in config.autoload_models_in.Route Callback Overrides:
setRouteCallback in the service provider doesn’t work, ensure it’s called after the booted event:
$this->app->booted(fn () => Linkable::setRouteCallback(...));
Locale Fallbacks:
disable_localization: false but routes lack locale support, URLs may fail. Test with:
$post->url('en'); // Force locale
How can I help you explore Laravel packages today?