hindbiswas/laravel-model-utils

A lightweight Laravel package for Model traits and utilities

Maintainers

Package info

github.com/hind-sagar-biswas/laravel-model-utils

pkg:composer/hindbiswas/laravel-model-utils

Fund package maintenance!

Hind Biswas

Statistics

Installs: 22

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v1.1.1 2026-04-20 09:31 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

A lightweight Laravel package that adds practical Eloquent model traits and utilities.

Installation

composer require hindbiswas/laravel-model-utils

Requirements

  • PHP 8.3+
  • Laravel 11, 12, or 13

Included Features

  • BelongsToAuth: auto-assign user_id when an authenticated user creates a model.
  • Optionable: convert model records to value/label option arrays.
  • Filterable: dynamic exact filters plus relationship-aware search with dot notation.
  • HasSlug: generate slugs from model attributes with optional uniqueness and update behavior.
  • Archivable: archive/restore records with query scopes for active and archived models.
  • EnumUtil: convert PHP enums to arrays, CSV, associative maps, and options.

Usage

BelongsToAuth Trait

use HindBiswas\ModelUtils\Traits\BelongsToAuth;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use BelongsToAuth;

    protected $guarded = [];
}

Behavior:

  • If user_id is empty and a user is authenticated, user_id is filled from Auth::id().
  • If user_id is already present, it is not overridden.

Optionable Trait

use HindBiswas\ModelUtils\Traits\Optionable;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use Optionable;

    protected $guarded = [];
}
Category::options();
// [
//   ['value' => 1, 'label' => 'Books'],
//   ['value' => 2, 'label' => 'Games'],
// ]

You can override the columns used for options:

protected static function optionValue(): string
{
    return 'code';
}

protected static function optionLabel(): string
{
    return 'title';
}

Filterable Trait

use HindBiswas\ModelUtils\Traits\Filterable;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use Filterable;

    protected $guarded = [];

    protected array $filterable = [
        'status',
        'author.name',
        'author.organization.name',
    ];

    protected array $searchable = [
        'title',
        'author.name',
        'author.organization.name',
    ];
}
$articles = Article::query()->filter([
    'status' => 'published',
    'author.name' => 'Alice',
    'search' => 'Laravel',
])->get();

Notes:

  • Exact filters use where and whereHas.
  • Search uses grouped orWhere and orWhereHas with %search% matching.
  • Dot notation supports nested relations like owner.user.name.

HasSlug Trait

use HindBiswas\ModelUtils\Traits\HasSlug;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasSlug;

    protected $guarded = [];

    protected function slugSource(): array
    {
        return ['title'];
    }

    protected function slugUniqueScope(): array|bool
    {
        return true;
    }

    protected function updateSlugOnUpdate(): bool
    {
        return true;
    }
}

Available hooks:

  • slugField(): slug column name (default: slug).
  • slugSource(): source attributes used to build slug.
  • slugUniqueScope(): controls uniqueness.
    • false: no uniqueness checks.
    • true: unique across table.
    • ['tenant_id']: unique within a scope.
  • maxSlugLength(): maximum slug length (default: 255).
  • updateSlugOnUpdate(): regenerate slug when source fields change.

getRouteKeyName() returns slugField().

Archivable Trait

use HindBiswas\ModelUtils\Traits\Archivable;
use Illuminate\Database\Eloquent\Model;

class Document extends Model
{
    use Archivable;

    protected $guarded = [];
}

Your table should have an archived_at nullable datetime column.

Schema::table('documents', function (Blueprint $table) {
    $table->dateTime('archived_at')->nullable();
});
$document->archive();
$document->restoreArchive();
$document->isArchived();

Document::query()->onlyArchived()->get();
Document::query()->withArchived()->get();

Behavior:

  • Active records are returned by default because a global scope excludes archived rows.
  • onlyArchived() fetches only archived records.
  • withArchived() fetches both active and archived records.
  • archived_at is cast to datetime automatically.

EnumUtil Helper

use HindBiswas\ModelUtils\Utils\EnumUtil;

enum OrderStatus: string
{
    case Draft = 'draft';
    case Published = 'published';
}

EnumUtil::toArray(OrderStatus::class);
// ['draft', 'published']

EnumUtil::toCSV(OrderStatus::class);
// 'draft,published'

EnumUtil::toAssocArray(OrderStatus::class);
// ['draft' => 'Draft', 'published' => 'Published']

EnumUtil::toOptions(OrderStatus::class);
// [
//   ['value' => 'draft', 'label' => 'Draft'],
//   ['value' => 'published', 'label' => 'Published'],
// ]

If enum cases implement a label() method, EnumUtil uses it. Otherwise, labels are generated using Str::headline(...).

Changelog

Please see CHANGELOG for details.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

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