How to Customize User Model in Laravel
Tags: laravel, php, craftablepro, webdev
Published: 2024-03-25 07:00:00



Packages are the cornerstone of PHP and Laravel development. Without them, we would have to reinvent (or copy&paste) the wheel for every single project. However, a package may not fully meet all your needs, so it's important that they are well-built and easily customizable.

One of the very well-known and one of our favorite packages is the [Laravel-medialibrary from Spatie](https://spatie.be/docs/laravel-medialibrary/introduction). The main model the package works with is called `Media`. 90% of the time, it fulfills all of our project’s needs, but for the remaining 10% we need to extend or tweak its functionality and that’s where the “swappability” feature comes into play.

If you are interested in how it’s implemented, feel free to head to its source code and browse. We will mention a simplified version of how we did it for our package further in this article.

---

Craftable PRO is an advanced Laravel admin panel and CRUD generator that goes beyond simple roles and permissions management. With Craftable PRO, you can effortlessly generate a fully functional admin panel based on your database structure. Additionally, it streamlines media management, facilitates translation handling, and offers various other powerful features. Simplify the development process of your entire Laravel backend with Craftable PRO.

---

One of the most important out-of-the-box models in Craftable PRO is definitely the `CraftableProUser`, which is the default authenticatable model. In the beginnings of the package history, however, it used to reside inside the vendor package and be hard-coded in many places of the codebase - and attempting to extend its functionality or swap it for a different model would have proved to be a great challenge.

We have implemented this feature recently and it turned out to be easier than expected.

The gist of it is as follows:

**1. Add an attribute to config for the model’s class name:**

```
'craftable_pro_user_model' => Brackets\CraftablePro\Models\CraftableProUser::class
```

**2. Create a base model which contains only the necessary code without which the package would not function:**

```
<?php

namespace Brackets\CraftablePro\Models;

use Brackets\CraftablePro\Helpers\Initials;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class BaseCraftableProUser extends Authenticatable
{
    use HasRoles;
    
    protected $guard = 'craftable-pro';

    protected $fillable = [
        'first_name',
        'last_name',
        'email',
    ];

    protected function initials(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => Initials::new()->generate($this->first_name . ' ' . $this->last_name)
        );
    }
}
```

**3. Replace all hard-coded mentions of the model with the config value. Instead of:**

```
$user = CraftableProUser::updateOrCreate([
  'email' => $email,
], [
  'first_name' => 'Administrator',
]
```

do:

```
$user = config('craftable-pro.craftable_pro_user_model')::updateOrCreate([
  'email' => $email,
], [
  'first_name' => 'Administrator',
]
```

Now, let’s go and see how this new feature can be configured and used within Craftable Pro. For a quicker overview, the official documentation is located here: [Users – Craftable PRO Documentation
](https://docs.craftable.pro/basic-features/users#custom-user-model)
**Task:** add a `full_name` attribute and a `teams` relationship to the authenticatable user model without having to rewrite any package code.

**Step 1. Create a new Eloquent model extending the base class**

```
<?php

namespace App\Models;

use Brackets\CraftablePro\Models\BaseCraftableProUser;

class AdminUser extends BaseCraftableProUser
{
    protected $table = 'craftable_pro_users';
}
```

It can also be called the same as the original `CraftableProUser` model, since it is in a different namespace. Despite that, we will call it `AdminUser` for clarity.

If you want to keep using the underlying `craftable_pro_users` database table, you have to specify it because it does not get inherited from the base model.

**Step 2. Register the new model in the config/craftable-pro.php file**

If you don’t see the `config/craftable-pro.php` file, it first has to be published through the `php artisan vendor:publish --tag=craftable-pro-config` command.

```
return [
    /*
     * The fully qualified class name of the Craftable Pro user model.
     */
    'craftable_pro_user_model' => App\Models\AdminUser::class,
    ...
];
```

Step 3. Configure the guards and auth providers in `config/auth.php` file:

Unfortunately (but understandably) there is no way to use a config value from one config file within another config file, so we have to specify the new model here as well.

```
use App\Models\AdminUser;

return [
  'providers' => [
        'craftable-pro-users' => [
            'driver' => 'eloquent',
            'model' => MyCraftableProUser::class,
        ],
  ],
  'guards' => [
        'craftable-pro' => [
            'driver' => 'session',
            'provider' => 'craftable-pro-users',
        ],
  ],
  ...
]
```

**Step 4. Customize the new AdminUser model however you like**

Add attributes, relationships, appends, eager loading… - whatever you want.

```
<?php

namespace App\Models;

use Brackets\CraftablePro\Models\BaseCraftableProUser;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class AdminUser extends BaseCraftableProUser
{
    protected $table = 'craftable_pro_users';

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->appends = [...parent::getAppends(), 'full_name'];
        $this->with = ['teams'];
    }

    public function getFullNameAttribute(): string
    {
        return $this->first_name . ' ' . $this->last_name;
    }

    public function teams(): BelongsToMany
    {
        return $this->belongsToMany(Team::class);
    }
}
```

**Step 5a. Update data in the database where necessary, mainly in polymorphic relationships:**

![Database OLD](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mg5x91u305in29h4j0xj.png)

to

![Database NEW](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2ltwg179t2bb3hyps82.png)


**Step 5b. If you are already using Laravel’s morph map, update the model class name instead:**

```
Relation::morphMap([
  'CraftableProUser' => CraftableProUser::class
]);
```

to

```
Relation::morphMap([
  'CraftableProUser' => App\Models\AdminUser::class
]);
```

**Customizing the user listing**

To customize the user listing (Access tab), it is necessary to first copy the vue code from v`endor/brackets/craftable-pro/resources/js/Pages/CraftableProUser/Index.vue` and replace with it the code in `resources/js/craftable-pro/Pages/CraftableProUser/Index.vue` as the originally published `resources/js/craftable-pro/Pages/CraftableProUser/Index.vue `file is only a stub that links to the vendor file.

Let’s replace the name with the new `full_name` attribute:

```
<ListingDataCell>
    <div class="flex items-center">
        <Avatar
            :src="item.avatar_url"
            :name="`${item.first_name} ${item.last_name}`"
        />
        <div class="ml-4">
            <div class="font-medium text-gray-900">
                <!-- TODO: maybe have full_name attribute? -->
                {{ item.first_name }} {{ item.last_name }}
            </div>
            <div class="text-gray-500">{{ item.email }}</div>
        </div>
    </div>
</ListingDataCell>
```

This code can now be refactored as such:

```
<ListingDataCell>
    <div class="flex items-center">
        <Avatar
            :src="item.avatar_url"
            :name="item.full_name"
        />
        <div class="ml-4">
            <div class="font-medium text-gray-900">
                {{ item.full_name }}
            </div>
            <div class="text-gray-500">{{ item.email }}</div>
        </div>
    </div>
</ListingDataCell>
```

Don’t forget to run the `npm run craftable-pro:build` command and you are done!

In summary, packages are vital components in PHP and Laravel development, streamlining processes and saving valuable time by providing pre-built functionalities.

The recent integration of this feature into Craftable PRO, particularly with the `CraftableProUser` model, highlights the importance of well-designed and adaptable packages. What was once a complex task has now become manageable, thanks to thoughtful design and implementation.