graham-campbell/database
A Laravel database package from Graham Campbell that provides a streamlined, configurable database layer with multiple connections and seamless integration with Laravel's query builder and Eloquent for simple, consistent data access.
Read this first: graham-campbell/database is an archival Laravel 4.2 package. The repo lives under GrahamDeprecated, the only release is v0.1.4-alpha, and it has zero dependents. There is no forward path to Laravel 5+. The only two legitimate reasons to touch it are (a) you are already maintaining a frozen L4.2 app that depends on it, or (b) you want a small, readable reference for a repository layer you intend to write yourself. Everything below assumes one of those.
Minimal install (legacy L4.2 app only):
Require it with the alpha flag — the tag is not stable, so Composer will refuse to resolve it otherwise:
composer require graham-campbell/database:~0.1@alpha
If your root composer.json still has "minimum-stability": "stable", either raise it to dev (and pin everything else) or rely on the @alpha inline flag. Expect PHP 5.4–5.6 constraints from the transitive illuminate/* pins.
Register the provider in app/config/app.php under providers:
'GrahamCampbell\Database\DatabaseServiceProvider',
Confirm the exact FQCN against src/ in the checkout — the README is thin, and the provider name is the very first thing to verify. Register it after any provider you depend on.
Publish config only if the package actually ships one:
php artisan config:publish graham-campbell/database
If src/config/ doesn't exist, skip this — don't invent a config file.
Where to look first: open the base repository class in src/ (typically something like GrahamCampbell\Database\Repository). Its method signatures — find, findOrFail, create, update, delete, paginate — are the real documentation. Read the constructor too; that's where the model/connection resolution contract lives, and it's the part that determines how you wire subclasses.
First use case — thin out one fat controller. Pick a simple model and make a repository:
use GrahamCampbell\Database\Repository;
class PostRepository extends Repository
{
protected $model = 'Post';
}
Then resolve and use it:
$post = App::make('PostRepository')->find($id);
Verify a full round-trip (create → find → update → delete) on a throwaway record before widening adoption to other controllers.
One repository per aggregate root. PostRepository, UserRepository, OrderRepository — each extending the base and declaring its model. Resist a god-repository; the package gives you composition for free and the L4.2 app/repositories/ convention maps onto it cleanly.
Bind interfaces, inject repositories. Don't call App::make() in controllers. In a RepositoryServiceProvider registered after the package provider:
App::bind('PostRepositoryInterface', 'PostRepository');
Controllers then type-hint the interface:
public function __construct(PostRepositoryInterface $posts)
{
$this->posts = $posts;
}
This is the entire payoff of the package — a single seam you can swap, mock, or decorate later.
Route all reads/writes through the base helpers. The daily flow should never reach for Post::find() directly:
$post = $this->posts->find($id);
$post = $this->posts->findOrFail($id);
$new = $this->posts->create(Input::all());
$updated = $this->posts->update($id, Input::all());
$this->posts->delete($id);
If a helper you expect isn't in the base class, add it on your subclass rather than leaking Eloquent into the controller.
Keep pagination and limit policy in the repository. Wrap paginate($perPage) and limit($n) so page-size lives in one place. Controllers should ask for "a page" without knowing the number:
public function index()
{
return View::make('posts.index', ['posts' => $this->posts->paginate(20)]);
}
Changing the default page size then becomes a one-line edit, not a grep.
Use the cache-aware read paths deliberately. If the base repository exposes cached reads, treat that as an opt-in per method rather than a global default — wrap it in a named method on your subclass (findCached($id)) so the call site documents the staleness contract. Don't let caching appear implicitly on find().
Testing seam. Because everything resolves through the container, tests can App::instance('PostRepositoryInterface', $mock) and assert controller behaviour without a database. This is the strongest argument for the pattern — and the one worth copying even if you drop the package. For the persistence layer itself, integration-test the repository against a real (SQLite) database; unit-test controllers against a mock.
Porting the idea to modern Laravel (the recommended path). The abstraction is ~150–250 lines. In a Laravel 10/11 app, write it in-house:
abstract class BaseRepository
{
public function __construct(protected Model $model) {}
public function find(int $id): ?Model { return $this->model->find($id); }
public function paginate(int $perPage = 20) { return $this->model->paginate($perPage); }
// create/update/delete...
}
Then use constructor injection and interface bindings in a service provider — the same shape, but PHP 8+, typed, tested, and yours. You get the design without inheriting the abandonment.
Connection resolution. If the package resolves connections dynamically (that's one of its advertised features), use it only when you genuinely run multi-database apps — e.g., a read replica or a tenant-per-database setup. For single-connection apps, leave the default alone; overriding connection resolution without a need adds an invisible variable to every query.
composer require without @alpha (or minimum-stability: dev) will fail with a confusing "could not find a matching version" error. That's the tag, not the package name.GrahamDeprecated/* in the repo path is the maintainer explicitly signalling archival status. Treat any bug you find as permanently yours; there is no upstream to file against.RepositoryServiceProvider; otherwise the base class bindings may not exist when your bindings are evaluated.create/update helpers almost certainly delegate to Eloquent's fill, so $fillable/$guarded on the model is your only protection. Never pass Input::all() straight into create() on a model with a permissive $guarded = [].DB::listen() and log the SQL.src/ and check the autoload standard (PSR-0 vs PSR-4) in composer.json.find(), call the invalidator in update()/delete() — this is the classic stale-read source, and it's easy to miss because the reads look fine in dev.composer update. This package pins illuminate/* to the 4.2 line and PHP to 5.x. A blanket update will either fail resolution or drag unrelated packages backwards. Pin and update surgically.vendor/. If you find yourself editing vendor/, you're already better off reimplementing the class.How can I help you explore Laravel packages today?