bytetcore/serpo

Laravel Repository Pattern - Elegant data layer abstraction with criteria-based filtering for Eloquent models.

Maintainers

Package info

github.com/ByteTCore/serpo

Documentation

pkg:composer/bytetcore/serpo

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.0.1 2026-04-06 10:27 UTC

This package is auto-updated.

Last update: 2026-04-06 10:30:15 UTC


README

Latest Version on Packagist License PHP Version Latest Stable Version Total Downloads Latest Unstable Version

Elegant data layer abstraction for Laravel using the Repository Pattern with powerful criteria-based filtering. Keep your controllers clean and your data logic reusable.

Features

  • 🏗️ Repository Pattern — Abstract Eloquent models behind clean interfaces
  • 🔍 Criteria System — Composable, reusable query filters (where, like, date, JSON, null, in)
  • Zero Boilerplate — Artisan generators for repositories, services, and criteria
  • 🔗 Fluent Chaining — Chain any Eloquent Builder method directly on repositories
  • 🔄 Auto Query Reset — Query state resets after execution, preventing stale queries
  • 📦 Laravel Auto-Discovery — Install and go, no manual provider registration

Requirements

  • PHP 8.1+
  • Laravel 9.0+

Installation

composer require bytetcore/serpo

Publish the configuration:

php artisan vendor:publish --tag=serpo-config

Quick Start

1. Generate a Repository

# Basic repository
php artisan make:repository UserRepository

# With a specific model
php artisan make:repository UserRepository --model=User

# With a corresponding service class
php artisan make:repository UserRepository --model=User --service

2. Generate a Service

php artisan make:service UserService

3. Generate a Custom Criteria

php artisan make:criteria ActiveUserCriteria

Usage

Basic Repository

namespace App\Repositories;

use App\Models\User;
use ByteTCore\Serpo\Repositories\BaseRepository;

class UserRepository extends BaseRepository
{
    public function __construct(User $model)
    {
        parent::__construct($model);
    }
}

Query Methods

$repo = app(UserRepository::class);

// Get all records
$users = $repo->all();

// Get with specific columns
$users = $repo->get(['id', 'name', 'email']);

// Get first record
$user = $repo->first();

// Get first or throw exception
$user = $repo->firstOrFail();

// Get latest record
$user = $repo->last();
$user = $repo->last('updated_at');

Eloquent Builder Chaining

All Eloquent Builder methods are available directly on the repository:

$users = $repo->where('active', true)
    ->orderBy('name')
    ->limit(10)
    ->get();

$count = $repo->where('role', 'admin')->count();

$users = $repo->with('posts')->whereHas('posts')->get();

Criteria-Based Filtering

Define reusable filter conditions in your repository:

use ByteTCore\Serpo\Criteria\WhereCriteria;
use ByteTCore\Serpo\Criteria\LikeCriteria;
use ByteTCore\Serpo\Criteria\DateCriteria;
use ByteTCore\Serpo\Constants\Filter;

class UserRepository extends BaseRepository
{
    protected array $conditions = [
        // Simple equality (default operator is '=')
        'status' => WhereCriteria::class,

        // Search across multiple columns
        'keyword' => [
            'class' => LikeCriteria::class,
            'columns' => 'name|email',
            'boolean' => Filter::OR,
            'pattern' => Filter::CONTAINS,
        ],

        // Comparison operators via params
        'min_age' => [
            'class' => WhereCriteria::class,
            'columns' => 'age',
            'operator' => Filter::GTE,
        ],

        // Date filtering
        'created_after' => [
            'class' => DateCriteria::class,
            'columns' => 'created_at',
            'operator' => Filter::GTE,
        ],
    ];

    public function __construct(User $model)
    {
        parent::__construct($model);
    }
}

Apply filters from request data:

// In your controller
$users = $repo->filters($request->only(['status', 'keyword', 'min_age']))->get();

Available Criteria

Criteria Description Params
WhereCriteria WHERE col op val operator: =, <>, >, >=, <, <=
LikeCriteria WHERE col LIKE pattern boolean: or, and; pattern: contains, starts_with, ends_with
DateCriteria WHERE DATE(col) op val operator: =, <>, >, >=, <, <=
BetweenCriteria WHERE col BETWEEN val[0] AND val[1]
NotBetweenCriteria WHERE col NOT BETWEEN val[0] AND val[1]
YearCriteria WHERE YEAR(col) op val operator: =, <>, >, >=, <, <=
MonthCriteria WHERE MONTH(col) op val operator: =, <>, >, >=, <, <=
InCriteria WHERE col IN (...)
NotInCriteria WHERE col NOT IN (...)
NullCriteria WHERE col IS NULL
NotNullCriteria WHERE col IS NOT NULL
JsonContainsCriteria whereJsonContains
JsonNotContainsCriteria whereJsonDoesntContain
OrderByCriteria ORDER BY col (asc/desc)

Detailed Criteria Walkthrough

Database Comparisons

'status' => WhereCriteria::class, // Checks if 'status' matches input
'price_over' => [
    'class' => WhereCriteria::class,
    'columns' => 'price',
    'operator' => Filter::GTE // ->where('price', '>=', input)
]

Search & Text (LikeCriteria)

Automatically supports SQL padding (%) so you don't have to inject % in strings.

'keyword' => [
    'class' => LikeCriteria::class,
    'columns' => 'title|content|author',
    'boolean' => Filter::OR, // ->where('title', 'like', '%val%') OR ->where('content', ...)
    'pattern' => Filter::CONTAINS, // Optional. Other options: STARTS_WITH, ENDS_WITH
]

Ranges (BetweenCriteria)

Expects an array of precisely two values: [$start, $end].

'price_range' => [
    'class' => BetweenCriteria::class,
    'columns' => 'price' // expects $request->price_range = [100, 500]
]

Working with Dates

Filter via specific date, year, or month. Extremely useful for reporting dashboards.

'created_at' => DateCriteria::class, // Date match YYYY-MM-DD
'birth_year' => YearCriteria::class,
'birth_month' => MonthCriteria::class,

Sets & Arrays

Checks whether a column is among an array of inputs.

'tags' => InCriteria::class,    // Expects $request->tags = ['php', 'laravel']
'ignore' => NotInCriteria::class, // Exclude IDs

Nullity Checks

Pass a truthy value (true, 1) to trigger these.

'unverified' => NullCriteria::class,    // ->whereNull('email_verified_at')
'active_only' => NotNullCriteria::class // ->whereNotNull('email_verified_at')

JSON Columns

Filter based on JSON arrays directly in the database.

'has_tag' => JsonContainsCriteria::class, // ->whereJsonContains('tags', input)

Dynamic Sorting (OrderByCriteria)

Pass Filter::ASC or Filter::DESC to dynamically sort records dynamically instead of hardcoding ->orderBy().

'sort_date' => [
    'class' => OrderByCriteria::class,
    'columns' => 'created_at' // expects $request->sort_date = Filter::DESC
]

Custom Criteria

namespace App\Criteria;

use ByteTCore\Serpo\Criteria\BaseCriteria;
use Illuminate\Database\Eloquent\Builder;

class ActiveWithRecentPostsCriteria extends BaseCriteria
{
    public function apply(Builder $query): void
    {
        $query->where('active', true)
            ->whereHas('posts', fn (Builder $q) => $q->where('created_at', '>=', now()->subDays(30)));
    }
}

Auto-Reset Behavior

By default, the query builder resets after each execution to prevent stale state:

$active = $repo->where('active', true)->get();   // query resets after get()
$all = $repo->all();                              // fresh query — returns all records

Disable auto-reset when you need to reuse the query:

$repo->withoutAutoReset()
    ->where('active', true);

$count = $repo->count();       // same filtered query
$users = $repo->get();         // same filtered query

Configuration

// config/serpo.php
return [
    'repository' => [
        'namespace' => env('SERPO_REPOSITORY_NAMESPACE', 'Repositories'),
    ],
    'service' => [
        'namespace' => env('SERPO_SERVICE_NAMESPACE', 'Services'),
    ],
    'criteria' => [
        'namespace' => env('SERPO_CRITERIA_NAMESPACE', 'Criteria'),
    ],
];

Testing

composer test

Changelog

See CHANGELOG.md for release history.

Contributing

See CONTRIBUTING.md for guidelines.

License

Licensed under the Apache License 2.0.