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

Odbc Driver Laravel Package

dbt/odbc-driver

dbt/odbc-driver adds ODBC database support to Laravel by registering a custom driver, letting apps connect to ODBC data sources through Laravel’s DB manager, query builder, and Eloquent. It uses familiar DSN- and credential-based connection configuration, with PDO ODBC under the hood.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Confirm the environment before touching Composer. ODBC is two layers: the PHP extension (pdo_odbc) and a system-level driver manager + vendor driver. Verify all three before debugging a single line of Laravel:

php -m | grep -i odbc            # pdo_odbc present?
odbcinst -q -d                 # unixODBC: listed drivers
isql -v MYDSN user pass        # connectivity + auth, outside Laravel entirely

If isql fails, Laravel never had a chance. On Windows use the ODBC Data Source Administrator instead of odbcinst/isql.

2. Install and let discovery register the provider.

composer require dbt/odbc-driver

This is a 29-star, zero-dependents package with a single maintainer — treat the version constraint as a real decision. Pin ^1.0 (or whatever the README documents) rather than *, and commit composer.lock.

3. Add a connection in config/database.php. The driver key is the contract with the package's DatabaseManager extension; everything else mirrors a normal Laravel connection. Check the README for the exact option names this version supports, but the shape is:

'odbc' => [
    'driver'   => 'odbc',
    'dsn'      => env('ODBC_DSN', 'Driver={ODBC Driver 18 for SQL Server};Server=...;Database=...;TrustServerCertificate=yes'),
    'username' => env('ODBC_USERNAME'),
    'password' => env('ODBC_PASSWORD'),
    'options'  => [
        PDO::ATTR_TIMEOUT => 10,
        PDO::ATTR_CASE    => PDO::CASE_NATURAL,
    ],
],

4. Smoke-test the connection in isolation.

php artisan tinker
>>> DB::connection('odbc')->select('select 1 as ok');

First real use case: read-only reporting against a legacy system — a SQL Server ERP, a DB2 warehouse, an AS/400 — where you cannot install a native PHP extension but the box already has an ODBC DSN. Keep the write path (migrations, Eloquent saves) out of scope until the read path is proven.

Implementation Patterns

Second connection, never the default. ODBC connections are typically slow, session-stateful, and point at systems you don't own. Keep DB::connection('odbc') explicit everywhere; do not make it the default connection, and never let a queued job silently inherit it.

Repository/import layer over raw Eloquent. Because the remote schema is rarely Laravel-conventions (no id, no created_at, weird casing), wrap it:

final class LegacyOrders
{
    public function recent(int $days): LazyCollection
    {
        return DB::connection('odbc')
            ->table('ORD_HDR')
            ->select(['ORD_NO', 'CUST_NO', 'ORD_DT'])
            ->where('ORD_DT', '>=', now()->subDays($days))
            ->orderBy('ORD_NO')
            ->lazy(500);
    }
}

Always bound your result sets. ->lazy() / ->chunk() with a stable orderBy on a unique-ish column. Never ->get() on an unbounded ODBC table — you will pull a decade of ERP rows into PHP memory and then time out mid-transfer.

Nightly sync into your own schema. The dominant pattern: a scheduled command reads from ODBC and upserts into a local MySQL/Postgres table with a real Eloquent model. Your app then gets indexes, relations, and migrations; the ODBC side stays a dumb pipe.

LegacyOrders::recent(2)->each(function ($row) {
    Order::updateOrCreate(['legacy_no' => trim($row->ORD_NO)], [...]);
});

Chunk the writes, batch the reads. Wrap the target-side writes in DB::transaction() in blocks of ~500 — a single transaction across a 200k-row sync will bloat the redo log.

Grammar/dialect alignment. ODBC is a transport, not a dialect: LIMIT vs OFFSET…FETCH vs TOP depends on the backend. If the package exposes a grammar or connection-class option, point it at the closest Laravel grammar (sqlsrv, postgres, mysql). If it ships a generic grammar, expect to lose whereDate, whereBetween on dates, JSON paths, and insertGetId — verify each before relying on it. Where the grammar gets it wrong, drop to ->select(DB::raw(...)) with bindings, not string interpolation.

Debug with DB::listen and the driver's own log. Laravel's listener gives you SQL and bindings; the actual ODBC error text comes from PDOException::getMessage() and often names the driver, not your table. Enable unixODBC tracing ([ODBC]\nTrace=Yes\nTraceFile=/tmp/odbc.log in ~/.odbc.ini or via ODBCINI) only for a single debugging session — it writes megabytes per second.

CI without the legacy system. Bind a LegacyOrders-style interface, and swap in a fake in tests. Do not attempt to run ODBC in CI unless you're prepared to containerize unixODBC + the vendor driver; most teams don't.

Gotchas and Tips

  • lastInsertId is unreliable or absent. Many ODBC drivers return "0" or throw. If you must write upstream, use insertGetId only after verifying against the actual backend; otherwise insert then SELECT the identity column back explicitly.
  • Unbuffered cursors break lazy loading. Some drivers refuse a second query while results are pending (Connection is busy with results for another command). If lazy() throws, force buffered queries via PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL or MYSQL_ATTR_USE_BUFFERED_QUERY-equivalents in options, and prefer chunk() (which closes each result set) over cursor().
  • Transactions may silently be no-ops. ODBC autocommit semantics vary per driver; DB::transaction() can appear to work and roll back nothing. Test rollback behavior explicitly before trusting it.
  • Schema:: is a coin flip. Migrations, hasTable, and the schema builder depend on grammar introspection that a generic ODBC grammar usually doesn't implement. Assume no migrations; manage the remote schema out-of-band.
  • Case sensitivity bites at the column level. Oracle/DB2 upper-case identifiers, Postgres lower-cases them. $row->order_no may be $row->ORDER_NO. Set PDO::ATTR_CASE deliberately and normalize in your mapping layer.
  • DSN strings with braces and semicolons must be quoted correctly in .env. Laravel's env() parser mangles ; and # in unquoted values — wrap the whole DSN in double quotes inside the env file, and clear config cache after any change: php artisan config:clear.
  • Timeouts stack. PHP max_execution_time, PDO ATTR_TIMEOUT, the ODBC driver's login timeout, and the DB's query governor are four independent limits. Set the PDO one first; it produces the clearest error.
  • Bush-league maintenance risk. Zero dependents and 29 stars means you are the QA department. Wrap the package behind your own DatabaseManager extension or a small service provider so a breaking change or an abandoned repo costs you an afternoon, not a rewrite. Pin the version, and read the diff on every bump.
  • Extension point to know: the package registers its connection resolver through a service provider — that's your hook for adding custom PDO options, a connection subclass, or a dialect-specific grammar without forking. Look there first when behavior needs to change.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route