DEV Community

Cover image for Linkeeper - Lesson 02 - Start playing with Filament
HappyToDev
HappyToDev

Posted on • Edited on • Originally published at happyto.dev

Linkeeper - Lesson 02 - Start playing with Filament

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,
            ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

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

  • 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.

a link has a name, a status (published or not) and obviously a link

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 :

  • a user can have 0, 1 or many links
  • a link (unique by its id) belongs to one and only one user

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
Enter fullscreen mode Exit fullscreen mode

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');
    }
};

Enter fullscreen mode Exit fullscreen mode

Let's migrate

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

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

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

php artisan migrate:rollback
Enter fullscreen mode Exit fullscreen mode

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

php artisan migrate:status
Enter fullscreen mode Exit fullscreen mode

Look at this screenshot captured before running the last migration.

Image description

We can see two things :

  • first the migration for the links table was not run already
  • second the three first migrations was launched in the same sequence : the sequence number [1]

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
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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),
        ];
    }
Enter fullscreen mode Exit fullscreen mode

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

In this code, we generate :

  • an url
  • a description based on a sentence of 5 words. In fact, as I let the second parameter of sentence method by default (true), sentence will generate sentences with approximately 5 words. It could be four, five, six or even seven words.
  • true or false for the published field with a minimum true percentage of 80%

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]);
            });
    }
Enter fullscreen mode Exit fullscreen mode

Some explanations :

  • First I create my own user to connect easily each time with my own email
  • Second part does this
    • create 10 fake users
    • for every user create between 2 and 10 links, during the creation we told that the user_id fiels must be filled with the current created user->id.

And now, the magic could begin.

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

php artisan db:seed
Enter fullscreen mode Exit fullscreen mode

Seeding database

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

You should see something like that :

  • users table
    Users table

  • links table

Links table

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

php artisan tinker
Enter fullscreen mode Exit fullscreen mode

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

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

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']);
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

Now, if in Tinker we use the following query :

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

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.

Well, we'll stop here for today.

In this longer chapter, we saw introduction to :

  • how to add functionnality to login page in Filament
  • models
  • factory
  • seeder
  • Tinker
  • scopes

As usual, I wait for your comments below.

See you.

Top comments (0)