PHP code example of teoprayoga / laravel-teorion

1. Go to this page and download the library: Download teoprayoga/laravel-teorion library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

teoprayoga / laravel-teorion example snippets


use Teoprayoga\Teorion\Filters\Filter;
use Teoprayoga\Teorion\QueryFilter;

class PostQueryFilter extends QueryFilter
{
    protected array $defaultSort = ['-created_at'];

    public function filters(): array
    {
        return [
            'search'     => Filter::multiLike(['title', 'description']),
            'status'     => Filter::enum('status', StatusEnum::class),
            'is_active'  => Filter::boolean()->default(true),
            'created_by' => Filter::exact(),
            'has_image'  => Filter::has('image'),
        ];
    }

    public function allowedScopes(): array
    {
        return ['published', 'popular'];
    }

    public function allowedWiths(): array
    {
        return ['author', 'comments', 'tags'];
    }

    public function allowedWithCounts(): array
    {
        return ['comments', 'reactions'];
    }

    public function allowedSorts(): array
    {
        return ['created_at', 'title', 'view_count'];
    }
}

use Teoprayoga\Teorion\Traits\Filterable;

class Post extends Model
{
    use Filterable;

    // Existing scopeXxx() methods stay here — whitelist controls which are exposed.
}

class Post extends Model
{
    use Filterable;

    protected string $queryFilter = CustomPostQueryFilter::class;
}

use Teoprayoga\Teorion\QueryFilter;

class Post extends Model
{
    use Filterable;

    public function newQueryFilter(): QueryFilter
    {
        return new PostQueryFilter();
    }
}

'query_filters_namespace' => 'App\\Filters\\Query',

public function index(GetRequest $request): mixed
{
    return Post::query()->filterAndPaginate($request);
    //                  ^^^^^^^^^^^^^^^^^^^^^^^^
    //                  applies all filters, scopes, withs, sorts,
    //                  and terminates with paginate() or get()
}

public function show(GetRequest $request, string $uuid): mixed
{
    return Post::findFiltered($request, $uuid);
}

'search'     => Filter::multiLike(['name', 'desc'])->alias('q'),
'is_active'  => Filter::boolean()->default(true),
'tenant_id'  => Filter::exact()->

// AppServiceProvider::boot()
FilterMacroRegistry::register('phone', function ($q, $value, $param) {
    return $q->where($param, preg_replace('/\D/', '', $value));
});

// In QueryFilter
'phone_number' => Filter::macro('phone'),

// Controller
use Teoprayoga\Teorion\Attributes\UsesQueryFilter;

class PostController
{
    #[UsesQueryFilter(PostQueryFilter::class)]
    public function index(GetRequest $request): JsonResponse { ... }
}

'strategies' => [
    'queryParameters' => [
        Strategies\QueryParameters\GetFromInlineValidator::class,
        Strategies\QueryParameters\GetFromQueryParamTag::class,
        \Teoprayoga\Teorion\Scribe\Strategies\UsesQueryFilterStrategy::class,
    ],
],

use Teoprayoga\Teorion\Concerns\HasQueryFilterRules;

class GetRequest extends FormRequest
{
    use HasQueryFilterRules;
    protected string $queryFilter = PostQueryFilter::class;

    public function rules(): array
    {
        return array_merge($this->queryFilterRules(), [
            // your custom rules
        ]);
    }
}

return [
    'default_per_page'         => 10,
    'paginate_key'             => 'is_paginate',
    'per_page_key'             => 'per_page',
    'max_results_key'          => 'max_results',
    'pagination_mode_key'      => 'pagination',
    'cursor_pagination_value'  => 'cursor',
    'cursor_name'              => 'cursor',
    'query_filters_namespace'  => 'App\\QueryFilters',
    'strict_mode'              => env('APP_DEBUG', false),
    'audit' => [
        'enabled'     => env('TEORION_AUDIT_ENABLED', false),
        'log'         => env('TEORION_AUDIT_LOG', false),
        'log_channel' => env('TEORION_AUDIT_LOG_CHANNEL', null),
        'sample_rate' => (float) env('TEORION_AUDIT_SAMPLE_RATE', 1.0),  // 0.0–1.0
    ],
    'fingerprint' => [
        'algorithm'    => env('TEORION_FINGERPRINT_ALGORITHM', 'sha256'),  // sha256 | xxh3 | xxh128
        'exclude_keys' => ['_token', '_method', 'page', 'cursor', 'signature', 'expires'],
    ],
];

// Filterable::scopeFilterAndPaginate return type
LengthAwarePaginator|CursorPaginator|Collection

use Teoprayoga\Teorion\Events\QueryAudited;

Event::listen(QueryAudited::class, function (QueryAudited $event) {
    // $event->record:
    //   fingerprint: ['hash', 'algorithm', 'payload']
    //   filter_class, model_class
    //   terminal_mode: 'paginate' | 'cursor' | 'collection' | 'find'
    //   limit, result_count, duration_ms, user_id
});

'fingerprint' => [
    'exclude_keys' => ['_token', '_method', 'page', 'cursor', 'signature', 'expires', 'your_custom_key'],
],

'fingerprint' => ['algorithm' => 'xxh3'],
// or: TEORION_FINGERPRINT_ALGORITHM=xxh3

use Teoprayoga\Teorion\Fingerprint\AlgorithmInterface;
use Teoprayoga\Teorion\Fingerprint\AlgorithmRegistry;

AlgorithmRegistry::register(new class implements AlgorithmInterface {
    public function name(): string { return 'blake3'; }
    public function hash(string $payload): string { return hash('blake3', $payload); }
});

Post::query()->filterAudited($request)->where('extra', $val)->get();
// → QueryAudited dispatched with terminal_mode='get'

class PersistQueryAudit
{
    public function handle(QueryAudited $event): void
    {
        QueryAuditLog::create([
            'fingerprint_hash' => $event->record['fingerprint']['hash'],
            'filter_class'     => $event->record['filter_class'],
            'duration_ms'      => $event->record['duration_ms'],
            'user_id'          => $event->record['user_id'],
            'recorded_at'      => now(),
        ]);
    }
}

Event::listen(QueryAudited::class, function (QueryAudited $event) {
    if ($event->record['duration_ms'] > 500) {
        Notification::route('slack', config('alerts.slack_webhook'))
            ->notify(new SlowQueryAlert($event->record));
    }
});

Event::listen(QueryAudited::class, function (QueryAudited $event) {
    $key = 'query:' . $event->record['fingerprint']['hash'];
    Redis::setex($key, 60, json_encode($event->record));
});

Event::listen(QueryAudited::class, function (QueryAudited $event) {
    static $seen = [];
    $hash = $event->record['fingerprint']['hash'];
    $seen[$hash] = ($seen[$hash] ?? 0) + 1;
    if ($seen[$hash] > 3) {
        Log::warning('Suspected N+1 — same query repeated', [
            'hash'  => $hash,
            'count' => $seen[$hash],
            'class' => $event->record['filter_class'],
        ]);
    }
});

Event::listen(QueryAudited::class, function (QueryAudited $event) {
    \Sentry\addBreadcrumb(new \Sentry\Breadcrumb(
        level: 'info',
        type: 'query',
        category: 'teorion',
        message: "Query: {$event->record['filter_class']}",
        metadata: ['duration_ms' => $event->record['duration_ms']],
    ));
});