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

Mssql Bundle Laravel Package

mediamonks/mssql-bundle

Symfony bundle that adds Microsoft SQL Server support to Doctrine DBAL. Provides a custom MSSQL driver built on pdo_sqlsrv or pdo_dblib, plus schema tooling, so your Symfony app can use SQL Server for entities, migrations, and queries.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing. This is a Symfony bundle (MssqlBundle extends Symfony\Component\HttpKernel\Bundle\Bundle), not a Laravel package. There is no service provider, no extra.laravel auto-discovery, and its DI extension will never run in Laravel. What you actually get is a small set of Doctrine DBAL driver/connection classes (under src/Driver and src/Connection, names shift between versions — read the tree before assuming) that:

  1. Wrap pdo_dblib (FreeTDS on *nix) or pdo_sqlsrv (Windows/ODBC) behind one driver name.
  2. Convert between the application's UTF-8 and SQL Server's UTF-16/UCS-2 nvarchar representation.
  3. Patch transaction begin/commit/rollback behaviour that the raw PDO drivers get wrong.

So "getting started" in a Laravel app is really "can I use the driver classes without the bundle?" — usually yes, because the bundle is thin orchestration around them.

Minimal steps

composer require mediamonks/mssql-bundle

Before you write a line of code, check two things:

composer show mediamonks/mssql-bundle   # what dbal constraint does it pin?
composer show doctrine/dbal             # what does your app already have?

If the bundle pins doctrine/dbal:^2 and your app (Laravel 11+) is on DBAL 3/4, stop — the drivers will not satisfy the interface. This is the single most common reason this package is unusable in a modern stack. Verify the constraint first; don't discover it at runtime.

Where to look first

  • src/Driver/* — the AbstractDriver / concrete driver you'd hand to DriverManager::getConnection(['driverClass' => ...]).
  • src/Connection/* — the subclass carrying the UTF-8 and transaction logic; this is the real value.
  • DependencyInjection/ and README example YAML — useful only to see which service ids and parameters the author intended, i.e. as documentation of options.
  • composer.json — PHP + DBAL version floors.

First use case

The canonical one: a Laravel app on MySQL/Postgres that needs to read from a legacy ERP/CRM on SQL Server — reporting screens, CSV exports, a nightly sync job. Read-only, one or two connections, no migrations. That is the shape this bundle was built for, and it's the shape where its quirks matter least.

A pragmatic first step that often makes the bundle unnecessary:

// config/database.php — Laravel's own connector, no bundle needed
'mssql' => [
    'driver'   => 'sqlsrv',           // requires pdo_sqlsrv + msodbcsql18
    'host'     => env('MSSQL_HOST'),
    'port'     => env('MSSQL_PORT', 1433),
    'database' => env('MSSQL_DATABASE'),
    'username' => env('MSSQL_USERNAME'),
    'password' => env('MSSQL_PASSWORD'),
    'charset'  => 'UTF-8',
    'options'  => [
        PDO::ATTR_STRINGIFY_FETCHES  => false,
        PDO::SQLSRV_ATTR_ENCODING    => PDO::SQLSRV_ENCODING_UTF8,
    ],
],

Reach for the bundle only when (a) you're stuck on FreeTDS/dblib and Microsoft's Linux ODBC driver isn't an option, or (b) you've been bitten by the transaction handling and want the known workarounds rather than writing your own.

Implementation Patterns

Pattern 1 — DBAL driver, no bundle. The cleanest reuse. Grab the driver class and use DBAL directly; the Symfony wiring is irrelevant.

use Doctrine\DBAL\DriverManager;

$conn = DriverManager::getConnection([
    'driverClass' => \MssqlBundle\Driver\PDOSqlsrv\Driver::class, // verify exact FQCN in src/
    'host'        => config('database.connections.mssql.host'),
    'dbname'      => config('database.connections.mssql.database'),
    'user'        => config('database.connections.mssql.username'),
    'password'    => config('database.connections.mssql.password'),
]);

Caveat: the bundle connection classes are where UTF-8 conversion lives, and DriverManager will instantiate its own Connection, not the bundle's MssqlConnection. If the conversion matters, you must register the connection class explicitly ('wrapperClass' in DBAL 3) or subclass.

Pattern 2 — bridge DBAL into the Laravel query builder. Best of both worlds: keep Eloquent and the query builder, but get the bundle's PDO underneath.

// app/Database/DbalConnector.php
class DbalConnector extends \Illuminate\Database\Connectors\Connector
{
    public function connect(array $config): PDO
    {
        $dbal = \Doctrine\DBAL\DriverManager::getConnection([
            'driverClass' => \MssqlBundle\Driver\PDOSqlsrv\Driver::class,
            'host' => $config['host'], 'dbname' => $config['database'],
            'user' => $config['username'], 'password' => $config['password'],
            'wrapperClass' => \MssqlBundle\Connection\MssqlConnection::class,
        ]);

        return $dbal->getNativeConnection();
    }
}

// AppServiceProvider::boot()
DB::extend('mssql-legacy', function (array $config, string $name) {
    $config['name'] = $name;
    $pdo = (new DbalConnector())->connect($config);

    return new \Illuminate\Database\Connection(
        $pdo, $config['database'], $config['prefix'], $config
    );
});

Now DB::connection('mssql-legacy')->table('dbo.Orders')->where(...) works, DB::listen works, model binding works. Keep this in one place — it's the seam you'll debug.

Pattern 3 — two connections, explicitly scoped. Don't try to make MSSQL the default connection. Wire a named connection, and confine MSSQL access to repository classes:

final class OrderReportRepository
{
    public function openOrders(): LazyCollection
    {
        return DB::connection('mssql-legacy')
            ->table('dbo.Orders')
            ->where('Status', 'O')
            ->orderBy('OrderId')
            ->cursor(); // streams; avoids loading 500k rows
    }
}

cursor() over get() matters here — pdo_sqlsrv and pdo_dblib both buffer aggressively, and unbounded get() on a legacy table is how you OOM a queue worker.

Pattern 4 — transactions and deadlock retries. SQL Server resolves deadlocks by killing a victim (error 1205). Laravel's DB::transaction($cb, $attempts) already recognises 1205 via DetectsConcurrencyErrors, so you get retries for free if the exception surfaces as a PDOException with the number in the message:

DB::connection('mssql-legacy')->transaction(function () {
    // write path
}, attempts: 3);

Under FreeTDS/dblib the message text differs from sqlsrv, and nested transactions behave differently — this is precisely the class of bug the bundle's MssqlConnection was written to paper over. Test nested DB::transaction calls explicitly; don't assume.

Pattern 5 — ETL / sync jobs. Chunk by primary key, not offset. OFFSET…FETCH requires SQL Server 2012+; on 2008 Laravel's SqlServerGrammar falls back to ROW_NUMBER(), which is slow on large tables.

DB::connection('mssql-legacy')->table('dbo.Customers')
    ->orderBy('CustomerId')
    ->chunkById(1000, fn ($rows) => $this->upsert($rows), 'CustomerId');

Integration tips

  • Keep MSSQL queries in raw SQL or the query builder; don't expect cross-connection whereHas/relations to work — they don't.
  • Cast at the boundary: SQL Server datetime comes back as a string, uniqueidentifier as a string, decimal as a string (correctly, with ATTR_STRINGIFY_FETCHES => false only affecting some drivers). Normalise into DTOs before it reaches your domain.
  • Schema introspection in Laravel 11 goes through DBAL — Schema::connection('mssql-legacy')->getTables() will exercise your driver choice. Test it early; it's a good canary for DBAL version mismatch.
  • CI: run mcr.microsoft.com/mssql/server:2022-latest in a service container and assert round-trip of a nvarchar row containing äöü 日本語 🙂. That single test catches most of what this bundle exists to fix.

Gotchas and Tips

  • It is not a Laravel package. No auto-discovery, no config publish, no service provider. Any README instruction telling you to register the bundle in AppKernel/bundles.php is inapplicable. Only the src/Driver and src/Connection classes transfer.
  • DBAL version pinning is the killer. A Symfony bundle from this era commonly targets DBAL 2.x. DBAL 3 changed the Driver/Connection interfaces materially. Confirm before you build anything on top; otherwise you're vendoring a fork.
  • UTF-8 is a config problem before it's a code problem. With FreeTDS you need tds version = 7.3 (or 7.4) in freetds.conf — earlier TDS versions cannot negotiate UTF-8 and you'll get ? for every non-ASCII character, silently. With pdo_sqlsrv you need CharacterSet=UTF-8 in the DSN or PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8. The bundle's conversion layer is a fallback, not a substitute for correct driver config.
  • Collation mismatch causes index scans. Comparing an nvarchar column to a varchar parameter forces implicit conversion and drops the index. Pass N'...'-equivalent values (which PDO does when encoding is set correctly) and check execution plans on slow reports.
  • datetime vs datetime2. datetime has 3.33ms rounding and a 1753 floor. If timestamps matter, use datetime2 on the SQL Server side; the driver won't fix precision for you.
  • Nested transactions and savepoints. SQL Server supports SAVE TRANSACTION, but PDO_SQLSRV's handling of savepoint names and rollbacks has historically been inconsistent. If you rely on DB::transaction nesting inside a job that also wraps a transaction, test rollback behaviour explicitly rather than trusting it.
  • Deadlock retries need idempotency. DB::transaction(..., attempts: 3) re-runs the closure. Any external side effect inside it (HTTP call, dispatch) will execute twice. Keep closures pure.
  • Stored procedures. SET NOCOUNT ON at the top of every proc, or rowCount() lies to you under dblib. Output parameters are significantly easier through the native sqlsrv API than through PDO; if your integration is proc-heavy, that's a reason to abandon PDO here.
  • Encryption defaults. SQL Server 2019+/Azure negotiates TLS and rejects self-signed certs by default. pdo_sqlsrv needs Encrypt=1;TrustServerCertificate=1 (or a properly installed CA). This bites on first connect and looks like a credentials error.
  • Port placement differs by driver. dblib wants host:1433 in the host string; sqlsrv takes port as a separate key. A single config/database.php block often can't serve both cleanly.
  • Debugging. DB::listen() and DB::enableQueryLog() work as usual once bridged. For FreeTDS, TDSDUMP=/tmp/freetds.log tsql -S host -U user -D db isolates driver-layer problems from Laravel-layer ones. Nine times out of ten the bug is in freetds.conf, not your PHP.
  • Extension point worth knowing. Rather than fighting the bundle's connection class, subclass it and override only what you need (encoding conversion, beginTransaction), or skip it and set the PDO attributes yourself. The bundle is small enough that a targeted 30-line subclass is often safer than adopting an unmaintained dependency.
  • Bus factor. 16 stars, zero dependents, MIT. Assume no upstream fixes. If you adopt it, vendor the driver classes you actually use into app/Database/Mssql/ with a comment pointing at the origin commit. That's cheaper than discovering a PHP 8.4 incompatibility during an unrelated upgrade.
  • When to skip it entirely. For greenfield work, the Microsoft ODBC driver for Linux plus pdo_sqlsrv plus Laravel's built-in sqlsrv connector covers UTF-8, transactions, and TLS without a third-party shim. Reserve this bundle for FreeTDS-constrained environments or existing installations that already depend on its transaction workarounds.
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/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
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