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. 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
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:
to
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 vendor/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.
Top comments (0)