calebdw/laravel-sql-entities
calebdw/laravel-sql-entities maps SQL query results to typed PHP entity classes in Laravel, with attribute casting, relationships, and query integration—ideal for raw SQL, legacy schemas, and read models without full Eloquent overhead.
composer require calebdw/laravel-sql-entities, then publish the config: php artisan vendor:publish --tag=sql-entities-config (check vendor/calebdw/laravel-sql-entities/README.md for the exact tag and provider registration).config/sql-entities.php (entity namespaces/paths, connection, driver gating) and at the package's src/Attributes directory — attributes are the primary configuration surface, so those classes are your API reference.php artisan list | grep -i sql before writing anything; the sync/discovery commands are what you'll actually run day-to-day.app/Sql/Entities (one class per entity, SQL body in a constant or heredoc), annotate it with the package's entity attribute, and run the sync command against your local DB. Once that round-trips, expand to functions/procedures/triggers only if your driver supports them.app/Sql/Entities/Views, /Functions, /Procedures, /Triggers, with the namespace registered in config. Discovery is class-scanning based, so a stable layout avoids surprises when the registry resolves entities.php artisan migrate --force, as its own deploy step — not inside a migration. Views frequently depend on columns added by migrations, and burying view sync in a migration makes rollbacks and re-runs painful.TestCase::setUp() after RefreshDatabase, run the entity sync programmatically so your test DB has the views/functions under test. This catches ordering problems between entities early.ReadsClassAttributes trait — not the framework one. See gotchas.ReadsClassAttributes is now shipped by the package (v1.1.1). Laravel's framework version of this trait is Laravel 13-only, so use Illuminate\...\ReadsClassAttributes; will fatal on Laravel 12. If you had a local copy or polyfill to work around the v1.1.0 breakage, delete it now — two definitions of the same trait name in your autoload path is a confusing way to spend an afternoon.Collection::get(). If you extend or wrap the entity collection/registry, pass a concrete Foo::class rather than a variable holding a class name, or PHPStan (at the levels this package now targets) will flag it. Same discipline applies to your own subclass code.composer dump-autoload (and clear config/route caches) after adding one, or the sync will silently do nothing for the file you just wrote.php artisan octane:reload — otherwise you'll debug "why is my SQL unchanged" for twenty minutes.CREATE OR REPLACE VIEW semantics (MySQL/MariaDB), and stored procedures (unsupported on SQLite) behave differently. Gate entities per driver in attributes/config, and keep a SQLite-compatible subset for tests or your test suite won't boot.CREATE VIEW/CREATE TRIGGER. Confirm grants before the first prod deploy rather than during it.DEFINER clauses on procedures are common sources of "works locally, fails in prod."How can I help you explore Laravel packages today?