Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Sql Function Repository Laravel Package

michael-rubel/laravel-sql-function-repository

A lightweight Laravel package that brings the repository pattern to SQL functions, letting you call database functions and stored procedures through clean, testable classes with bound parameters instead of scattered raw queries.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin, single-purpose abstraction: a repository class that executes PostgreSQL stored functions/procedures through Laravel's connection layer and binds parameters by name. It is archived (7 stars), so treat it as a reference implementation you vendor or fork rather than something you build a long-term architecture around. The 1.1.2 release (a compatibility bump adding Laravel 10 support) is the last meaningful activity — it does not change the archived status or the API surface.

Minimal steps:

  1. composer require michael-rubel/laravel-sql-function-repository:^1.1.2 — pin at least 1.1.2, which is the first release with Laravel 10 support in its constraints; 1.1.1 and below will cap you at older Laravel versions. Laravel 11/12 compatibility is still not guaranteed on an archived 2023 release. If it conflicts, copy the repository class into app/Repositories and keep the license header.
  2. Confirm the connection it will use points at PostgreSQL. The package assumes PG-specific syntax (named arguments, SETOF, RETURNS TABLE, composite types) and will not work against MySQL/SQLite.
  3. Verify the function exists and the DB user has EXECUTE on it:
    \df+ my_schema.get_active_users
    
  4. First use case — a table-returning function with named parameters:
use MichaelRubel\SqlFunctionRepository\SQLFunctionRepository;

$repository = new SQLFunctionRepository();

$rows = $repository->call('get_active_users', [
    'p_since' => now()->subDays(30)->toDateString(),
    'p_limit' => 100,
]);

Look first at the package's src/ — there is very little of it. The value is the named-parameter binding (:p_since), which avoids positional-argument mistakes when a function has five-plus parameters. Everything else (result mapping, typing, caching) you supply.

Implementation Patterns

One repository per function, not one generic wrapper. The package's base class is generic, but the useful pattern is to subclass it so the function name and parameter contract are fixed at the class level:

final class ActiveUsersRepository extends SQLFunctionRepository
{
    public function forPeriod(CarbonInterface $since, int $limit = 100): Collection
    {
        return collect($this->call('get_active_users', [
            'p_since' => $since->toDateString(),
            'p_limit' => $limit,
        ]));
    }
}

Subclasses give you a place for argument validation, casting, and a stable API for the rest of the app. Inject these repositories into services/actions; don't new them in controllers.

Wrap calls in typed DTOs when the function returns SETOF or RETURNS TABLE. PDO hands back stdClass rows with driver-dependent scalar types. Map immediately:

return collect($rows)->map(fn ($row) => new ActiveUser(
    id: (int) $row->id,
    name: (string) $row->name,
    lastSeenAt: Carbon::parse($row->last_seen_at),
));

Scalar and composite returns. SELECT * FROM fn(...) works for set-returning functions; for scalar functions prefer SELECT fn(:arg) AS result and read $rows[0]->result. For composite (RETURNS record) functions you generally need to expand fields explicitly: SELECT (fn(:arg)).*. Encapsulate that variation inside the repository subclass so callers see one method.

Transactions and read consistency. A function runs inside whatever transaction is open on the connection. If a function performs writes, wrap the repository call in DB::transaction(...) at the caller, not inside the repository — the repository should not own the transaction boundary. Conversely, don't call write-performing functions against a read replica connection.

Integration with Eloquent. These repositories don't hydrate models or support relations. Common hybrid: the function returns IDs or a filtered row set, then Model::whereIn('id', $ids)->with('relations')->get() re-hydrates. That's usually faster than making the function return every column.

Testing. Point the test suite at a real PostgreSQL database in CI (service container or pg_tmp). RefreshDatabase plus migrations that create the functions works, but functions are often owned by a separate migration track — keep a database/sql/functions/ directory and a migration that DB::unprepared(file_get_contents(...)). Mock-free tests of the repository are then straightforward.

Gotchas and Tips

  • Archived = frozen. No fixes for PHP 8.4 deprecations, Laravel 11/12, or PHPStan level bumps. Audit the source (it's a couple of files) before adopting; if you need it, fork and pin your own branch.
  • Pin ^1.1.2, and know what it bought you. 1.1.2 only relaxed the Laravel constraint to allow Laravel 10 — no API changes, no bug fixes, no deprecations, so upgrading from 1.1.1 is a composer update with no code migration. It does not signal renewed maintenance: Laravel 11+ is still unsupported, and any CI you inherit from a fork needs the constraint widened yourself.
  • The abstraction is small. DB::select('select * from get_active_users(:p_since, :p_limit)', [...]) does the same thing. Justify the dependency by the repository-per-function pattern above, not by the package itself. If you inline it, you delete the maintenance risk for free.
  • PostgreSQL only. Any test path that falls back to SQLite (:memory:) will break on named parameters and PG syntax. Guard those tests or mark the suite PG-required.
  • PDO named parameters and emulated prepares. Named binding is rewritten client-side; parameters used more than once in a statement, or with ambiguous types, can misbehave. If a function argument needs a type, cast explicitly in SQL — fn(:p_since::date) — or cast in PHP before binding.
  • Function overloading. PostgreSQL allows same-name functions with different arities/types. If get_users(text) and get_users(text, int) both exist, an omitted parameter silently resolves to the wrong one rather than erroring. Always pass every parameter the function expects, or name the function more specifically in the DB.
  • search_path. Unqualified function names resolve against search_path. Qualify with the schema (my_schema.get_active_users) in the repository so behavior doesn't change between environments where search_path differs.
  • Permissions. EXECUTE is granted per function. New functions added by a migration won't be callable by the app user until GRANT EXECUTE ON FUNCTION ... TO app_user runs. Add the grant to the same migration that creates the function.
  • Driver type coercion. Numeric and temporal columns come back as strings from libpq via PDO. Any arithmetic or strict comparison on raw results will behave surprisingly — cast in the mapping layer, uniformly.
  • Debugging. Log the generated SQL and bindings by wrapping call() in your subclass with DB::listen, or temporarily replace with DB::select and ->toRawSql() (Laravel 10+). Errors surface as QueryException with the function name but rarely the argument types — check pg_stat_activity and \df when a call fails with "function does not exist".
  • Extension points worth adding yourself: a caching decorator around the repository for expensive read-only functions, a LogQueries middleware, and a contract interface per repository so consumers depend on the interface rather than the concrete class. The package intentionally leaves all of this to you.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core