esign/laravel-linkable

Dynamically link Laravel models

1.2.0 2024-03-12 22:48 UTC

This package is auto-updated.

Last update: 2024-10-13 00:15:35 UTC


README

Latest Version on Packagist Total Downloads GitHub Actions

This package allows you to add a dynamic link to a Laravel model. This dynamic link may be a reference to another model or could also be an external URL. In essence, this package is just a Laravel relationship with some extra utility methods.

Installation

You can install the package via composer:

composer require esign/laravel-linkable

Usage

Preparing your model

To make a model have a dynamic link you may add the HasDynamicLink trait.

use Esign\Linkable\Concerns\HasDynamicLink;
use Illuminate\Database\Eloquent\Model;

class MenuItem extends Model
{
    use HasDynamicLink;
}

Your database structure should look like the following:

Schema::create('menu_items', function (Blueprint $table) {
    $table->id();
    $table->string('dynamic_link_type')->nullable();
    $table->string('dynamic_link_url')->nullable();
    $table->string('dynamic_link_linkable_model')->nullable();
});

Internal links

In case you set the dynamic_link_type as internal the dynamic_link_linkable_model field will be used. In order to know where a internal dynamic link should direct to you may implement LinkableUrlContract on the related model:

use Esign\Linkable\Contracts\LinkableUrlContract;

class Post extends Model implements LinkableUrlContract
{
    public function linkableUrl(): ?string
    {
        return "http://localhost/posts/{$this->id}";
    }
}

External links

In case you set the dynamic_link_type as external the dynamic_link_url field will be used. The value of this field should be a valid URL, e.g. https://www.example.com.

Storing linkables

Instead of using a regular MorphTo relation, this package ships with a SingleColumnMorphTo relation. Some CMS, including our own, do not allow for morphable relations based on two columns, e.g. dynamic_link_linkable_type and dynamic_link_linkable_id. The SingleColumnMorphTo combines both the type and id fields into a single column, e.g. dynamic_link_linkable_model. The value for this single column is stored in the {model}:{id} format, e.g. post:1. You're able to use both a fully qualified class name or a value from your application's morph map, just like a regular morphTo relation.

Note This approach is not ideal and more complex queries using this relationship may not work as expected. In case you're able to you may overwrite the dynamicLinkLinkable relation to use Laravel's default MorphTo relationship.

Linkables overview

To create an overview of all possible linkables you can create a MySQL view that creates a union of all possible models that can be linked to:

DB::statement('
    CREATE OR REPLACE VIEW linkables AS
    SELECT
        CONCAT("post:", id) AS id,
        "post" AS linkable_type,
        id AS linkable_id,
        CONCAT("Post - ", title) AS label
    FROM posts
    UNION
    SELECT
        CONCAT("comment:", id) AS id,
        "comment" AS linkable_type,
        id AS linkable_id,
        CONCAT("Comment - ", title) AS label
    FROM comments
');

This would create the following output:

Rendering dynamic links

This package ships with a view component that will help you render both internal and external links:

use Esign\Linkable\Concerns\HasDynamicLink;
use App\Models\Post;
use App\Models\MenuItem;

$post = Post::create();
$menuItemInternal = MenuItem::create([
    'dynamic_link_type' => HasDynamicLink::$linkTypeInternal,
    'dynamic_link_url' => null,
    'dynamic_link_linkable_model' => "post:{$post->id}",
]);

$menuItemExternal = MenuItem::create([
    'dynamic_link_type' => HasDynamicLink::$linkTypeExternal,
    'dynamic_link_url' => 'https://www.esign.eu',
    'dynamic_link_linkable_model' => null,
]);

The view component will render an <a> tag, when a model of the type external is given, the target="_blank" and rel="noopener" attributes will be applied.

<x-linkable-dynamic-link :model="$menuItemInternal">Esign</x-linkable-dynamic-link>
<a href="https://www.esign.eu/posts/1">Esign</a>
<x-linkable-dynamic-link :model="$menuItemExternal">Esign</x-linkable-dynamic-link>
<a href="https://www.esign.eu" target="_blank" rel="noopener">Esign</a>

Extending this package

Creating your own dynamic link types

This Laravel package offers built-in support for internal and external links by default. However, in certain scenarios, you might want to introduce custom link types, such as referencing an anchor tag. To achieve this, you can extend the package's functionality.

  1. Extend the HasDynamicLink Trait
namespace App\Models\Concerns;

use Esign\Linkable\Concerns\HasDynamicLink as BaseHasDynamicLink;

trait HasDynamicLink
{
    use BaseHasDynamicLink {
        dynamicLink as baseDynamicLink;
    }

    public static string $linkTypeAnchor = 'anchor';

    public function dynamicLink(): ?string
    {
        return match ($this->dynamicLinkType()) {
            static::$linkTypeAnchor => $this->dynamicLinkUrl(),
            default => $this->baseDynamicLink(),
        };
    }
}
  1. Create Your Own View Component

Next, you need to create your own view component that extends the DynamicLink component provided by the package. Here's how you can do that:

namespace App\View\Components;

use App\Models\Concerns\HasDynamicLink;
use Esign\Linkable\View\Components\DynamicLink as BaseDynamicLink;

class DynamicLink extends BaseDynamicLink
{
    public function render(): ?View
    {
        return match ($this->model->dynamicLinkType()) {
            HasDynamicLink::$linkTypeAnchor => view('linkable.dynamic-link-anchor'),
            default => parent::render(),
        };
    }
}
<a {{ $attributes->merge(['href' => $model->dynamicLink()]) }}>{{ $slot }}</a>
  1. Use Your Custom View Component

With your custom view component in place, you can now use it in your Blade templates instead of the one provided by the package:

<x-dynamic-link :model="$modelReferencingAnchorTag">
    My anchor tag
</x-dynamic-link>

Testing

composer test

License

The MIT License (MIT). Please see License File for more information.