LinKeeper an alternative to LinkTree with Laravel and Filament - Start playing with FilamentPHP
Par HappyToDev · 8 mins · Tutorial
Cet article existe aussi en version française 🇫🇷

Ok, now your application is set.

We can dive in something more concrete.

What we need to manage our links.

Remember what I said in the first part :

  • an user can signin, login, logout and manage his account
  • a user can see a list of their links
  • an user can add, edit or delete a link
  • an user can have a shareable link to share his links with the world
  • a link has a name, a status (published or not) and obviously a link. May be we will add some options later.

an user can signin, login, logout and manage his account

This is done with Filament. In fact, no completely, we can login, logout but not register or even manage our own account.

Signin view

But, you will see it's very easy to add register and profile page for users.

In app/Providers/Filament/AdminPanelProvider.php

Path to AdminPanelProvider.php

you have to add only 2 lines under login() function call to implement both functionnalities :

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->default()
            ->id('admin')
            ->path('admin')
            ->login()
            // Lesson 2 : here we add registration and profile page
            ->registration() // registration
            ->profile()      // profile management
            ->colors([
                'primary' => Color::Amber,
            ])
            ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
            ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
            ->pages([
                Pages\Dashboard::class,
            ])
            ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
            ->widgets([
                Widgets\AccountWidget::class,
                Widgets\FilamentInfoWidget::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->authMiddleware([
                Authenticate::class,
            ]);
    }
}

Go back to your login page : http://linkeeper.com/admin/login and you will see

Signin page with register link

that the link for signup has been added.

If you login, you will see under your menu the new sub menu 'Profile'

Image description

It allows you to edit your nickname, your e-mail address and change your password.

Image description

So, after this very cool step we can check the first functionnality :

✅ an user can signin, login, logout and manage his account

  • a user can see a list of their links
  • an user can add, edit or delete a link
  • an user can have a shareable link to share his links with the world
  • a link has a name, a status (published or not) and obviously a link. May be we will add some options later.

I know it is not in the order of the list, but first it's me writing, second I need to define table links and its fields before an user can add link, right ?

So let me do this ! 🤣

Let's think about table schema

Ok, what will be the fields in our table links ?

Of course an id, an url, a short description, a published status (published or not), a user_id foreign key and finally the classicals created_at and updated_at.

The user_id is to define the relationship between a user and a link.

In fact, we can resume this relation by :

This is why we use a one-to-many relationship.

What do you thing, is something missing ? Tell me in the comment if you think I forgot something.

Ok, now we have to prepare the migration for links table.

php artisan make:migration create_links_table
Image description

Now we have to edit this migration to add fields to the table. So, let's edit the migration file

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('links', function (Blueprint $table) {
            $table->id();
            $table->string('url');
            $table->string('description', 255)->nullable();
            $table->boolean('published')->default(false);
            $table->foreignId('user_id')->constrained('users')->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('links');
    }
};

Let's migrate

Ok, it's time to migrate. For that, we can use (again) Artisan.

php artisan migrate

Remember if you make something wrong or you want to modify immediately the schema, you can rollback your migration with :

php artisan migrate:rollback

And if you want to see the status of your migrations, you can use :

php artisan migrate:status

Look at this screenshot captured before running the last migration.

Image description

We can see two things :

Define model and relationship

The next step is to define the Link model and adapt User model.

We will use again Artisan :

php artisan make:model Link
Image description

Now we have to tell the model that a link belongs to an user.

Add this function to the model class Link:

    public function user()
    {
        return $this->belongsTo(User::class);
    }

To finish with the model, we have to update the model User by adding it a function links

    public function links()
    {
        return $this->hasMany(Link::class);
    }

We need fake datas to plays with

After defining our database schema, let's do something very important.

Create fake datas to play with. Please do not use production datas from your database during development. For many reasons such as ethics, confidentiality and security. We are professionals, so let's act accordingly.

The factories

By default Laravel comes with User factory, so we just need to create a Link factory.

php artisan make:factory Link

In the generated definition function we put this code :

    public function definition(): array
    {
        return [
            'url' => fake()->url(),
            'description' => fake()->sentence(5),
            'published' => fake()->boolean(80),
        ];
    }

Factories use Faker to generate fake datas but very close to real datas.

In this code, we generate :

The seeder

Laravel comes with database/seeders/DatabaseSeeder.php

Path to DatabaseSeeder.php

We have to edit it to use our factory.

Here it is the code :

    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        User::factory()->create([
            'name' => 'Happytodev',
            'email' => 'happytodev@gmail.com',
            'email_verified_at' => now(),
            'password' => Hash::make('Password*'),
            'remember_token' => Str::random(10),
        ]);

        User::factory()
            ->count(10)
            ->create()
            ->each(function ($user) {
                Link::factory()
                    ->count(rand(2, 10))
                    ->create(['user_id' => $user->id]);
            });
    }

Some explanations :

And now, the magic could begin.

Let's go back in your terminal, and launch simply this command :

php artisan db:seed
Seeding database

Go into your DB viewer, and check the tables users and links.

You should see something like that :

Links table

Tinker

If you want to check everything works fine, we can go a walk in Tinker.

php artisan tinker

Tinker will let us to play with our datas and check the relationship is correctly working.

User::find(6)->links()->get(['id', 'url', 'published', 'user_id']);

Here, we ask to find every link for the user with id = 6. We ask too to get only interesting fields.

Tinker query results

Seeing these results, we can improve immediately something.

What if, we only want the published links ?

So you can do it like this :

> User::find(6)->links()->where('published', 1)->get(['id', 'url', 'published', 'user_id']);

But there is a better way to do this and don't repeat yourself later. We can use Scopes.

A scope can add a bunch of query to your query. Scopes can be global or local. I let you consult the documentation to see more.

Here we will use a local scope.

Go to your link model and add this function :

    /**
     * Scope a query to only include published links.
     */
    public function scopePublished(Builder $query): void
    {
        $query->where('published', 1);
    }

Now, if in Tinker we use the following query :

User::find(6)->links()->published()->get(['id', 'url', 'published', 'user_id']);

we will obtain only the published links from user with id = 6.

Tinker query using scope

And voilà, we can cross this point in our list :

✅ an user can signin, login, logout and manage his account

  • a user can see a list of their links
  • an user can add, edit or delete a link
  • an user can have a shareable link to share his links with the world

✅ a link has a name, a status (published or not) and obviously a link. May be we will add some options later.

Conclusion

Well, we'll stop here for today.

In this longer chapter, we saw introduction to :

As usual, I wait for your comments below.

See you.

Cet article existe aussi en version française 🇫🇷

#laravel #filamentphp #php

Ce contenu t'a plu ou appris quelque chose ? 👀

Si la réponse est oui, alors tu peux peut-être me supporter 💪 ?

Il y a plusieurs façon de me supporter et certaines sont gratuites, je t'invite à les découvrir dans ma page dédiée 👇