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 Oci8 Laravel Package

yajra/laravel-oci8

Laravel-OCI8 is an Oracle database driver for Laravel that uses the OCI8 PHP extension to communicate with Oracle. It extends Illuminate/Database, adding Oracle connectivity and query support, with compatibility from Laravel 5.1 through 13.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites (the things that actually block you)

  • Oracle's OCI8 PHP extension must be compiled/enabled — it is not bundled with PHP. Verify with php -m | grep oci8. You also need Oracle Instant Client libraries available on the host (LD_LIBRARY_PATH on Linux, PATH on Windows).
  • Oracle 11g is the baseline; 12c/12cR2/19c/21c unlock better SQL generation (see server_version below).

Install

composer require yajra/laravel-oci8:^13

Match the package major to your Laravel major (the README has a straight compatibility table: Laravel 12 → ^12, Laravel 13 → ^13). The service provider (Yajra\Oci8\Oci8ServiceProvider) is auto-discovered on Laravel 5.5+, so registration is usually a no-op.

Configure

Publish the optional config (the package reads .env directly if you don't):

php artisan vendor:publish --tag=oracle

Then in .env:

DB_CONNECTION=oracle
DB_HOST=oracle.host
DB_PORT=1521
DB_SERVICE_NAME=orcl
DB_DATABASE=xe
DB_USERNAME=hr
DB_PASSWORD=hr
DB_SERVER_VERSION=19c

Add the matching connection block to config/database.php (driver => 'oracle', tns, host, port, database, service_name, charset, prefix, prefix_schema, server_version, …). Two connection styles are supported: a full TNS descriptor (tns) or discrete host/port/service_name.

First use case

Prove the connection and the driver registration before writing any domain code:

DB::connection('oracle')->select('select 1 from dual');
DB::connection('oracle')->getDriverName(); // "oracle"

Then the real first win: point an Eloquent model or a migration at it.

class Employee extends Model
{
    protected $connection = 'oracle';
    protected $table = 'employees';
    protected $primaryKey = 'employee_id';
}

Where to look first

  1. config/oracle.php after publishing — every env key in the README maps 1:1 to it.
  2. The Oracle-version section of the README, because server_version changes generated SQL (rownum vs fetch/offset, sequence vs identity, binary_ci).
  3. The dedicated docs at yajrabox.com/docs/laravel-oci8 — the README is a config reference, not a usage guide.
  4. changelog/ per version when you upgrade; several fixes materially change query output (e.g. whereDate, pagination count, hidden column filtering).

Implementation Patterns

Coexistence connection, not default connection

Most teams keep MySQL/Postgres as DB_CONNECTION and add Oracle as a named connection for the legacy system, ERP, or HR database:

// config/database.php
'oracle' => [ 'driver' => 'oracle', /* ... */ ],

Then be explicit everywhere: DB::connection('oracle'), ->on('oracle'), protected $connection = 'oracle'. Avoid swapping the default connection globally unless the whole app is Oracle-native — it silently changes behaviour of every package that assumes MySQL-flavoured SQL.

Model conventions for Oracle

  • Set $table in the exact stored case (typically uppercase or quoted). Unquoted identifiers are folded to uppercase by Oracle; quoted ones are case-sensitive.
  • Set $primaryKey and consider $keyType = 'string' / $incrementing = false when the PK is a sequence-populated NUMBER or a VARCHAR2 key.
  • Keep $dateFormat explicit if you use DATE columns; prefer TIMESTAMP columns for anything that needs sub-second precision, since DATE only stores seconds.
  • Use $guarded/$fillable normally; empty strings are a separate gotcha (see below).

Schema and migrations

Schema::connection('oracle')->create('employees', function (Blueprint $table) {
    $table->bigIncrements('employee_id'); // identity on 12c+, sequence otherwise
    $table->string('last_name', 100);
    $table->timestamp('hired_at')->nullable();
    $table->json('options')->nullable();   // CLOB, or native JSON on 21c
});
  • bigIncrements compiles to identity columns when server_version=12c+, and to sequence-backed defaults on 11g. This is the single biggest reason to set server_version correctly.
  • $table->json() maps to CLOB by default and to native JSON on 21c.
  • Migrations containing DDL are auto-committing in Oracle; don't expect a wrapping transaction to roll back a half-applied migration.
  • Schema introspection (compileColumns) had a fix to stop returning Oracle's internal hidden columns — pin a recent patch if Schema::getColumns() output looks noisy.

Query idioms that pay off daily

  • Pagination: on 12c+ the driver uses OFFSET … FETCH, on 11g it wraps with ROWNUM. Just use paginate(); don't hand-roll.
  • Date filtering: whereDate() compiles to TO_DATE(..., 'YYYY-MM-DD') with comparison-operator support; use it instead of where('created_at', 'like', ...).
  • Case-insensitive search: whereLike uses COLLATE BINARY_CI on 12cR2+, so ->whereLike('name', 'smith%') behaves case-insensitively without UPPER().
  • Bulk writes: upsert() handles CLOB values above 3999 chars by binding them as CLOB — a fix worth knowing about, because naive raw inserts will fail there.
  • insertGetId: empty value sets are compiled to values(DEFAULT) with the sequence, so don't fight it by supplying nulls manually.
  • Lateral joins: joinLateral is available on 12c+.

JSON workflows

DB::table('users')->where('options->theme', 'dark')->get();

DB::connection('oracle')->table('users')
    ->where('id', $id)
    ->update(['options->profile->settings->theme' => 'dark']); // 19c+

Treat JSON path updates as a 19c+ capability and enforce "one path per JSON column per update() call" in your code review checklist. On older versions, load the document, mutate it in PHP, and save the whole column.

Stored procedures and PL/SQL

The query builder handles normal DML; for procedures use DB::connection('oracle')->statement(...) with bindings, and DB::select() for cursor-returning calls. The package ships a PHPStan/Larastan extension (extension.neon) that teaches static analysis about the OCI8-specific DB methods — include it in phpstan.neon if you use Larastan, otherwise every DB::procedure(...)-style call is flagged.

Auth against Oracle

Oracle string comparison is case-sensitive by default, which breaks "forgot password" and login lookups that were developed on MySQL. Set the provider driver:

'providers' => [
    'users' => ['driver' => 'oracle', 'model' => App\User::class],
],

Multi-tenant / per-user credentials

The dynamic config key accepts a static callback that mutates $config before connect:

'dynamic' => [App\Models\Oracle\Config::class, 'dynamicConfig'],

This is the supported extension point for "each user has their own Oracle account" or per-schema routing. Resolve credentials from the authenticated user inside the callback, and be aware it runs on connection resolution — don't do expensive work there.

Testing

Keep the test suite on SQLite/MySQL when the feature doesn't need Oracle-specific SQL. For the parts that do (JSON paths, whereLike, sequences), create a dedicated oracle_testing connection, and prefer wrapping each test in a transaction over RefreshDatabase — Oracle DDL autocommit makes refresh cycles slow and occasionally surprising.

Gotchas and Tips

server_version is not cosmetic. It defaults to 11g and drives SQL generation: ROWNUM vs OFFSET/FETCH, sequence+trigger vs identity, BINARY_CI support, JSON path updates, native JSON columns. A 19c database left at the default will work but produce slow, legacy-shaped SQL. Set it per connection.

The OCI8 extension is the #1 setup failure. "could not find driver" almost always means the extension isn't loaded or Instant Client isn't on the library path — not a package bug. In Docker, install oci8 via PECL and copy Instant Client into the image; don't rely on the base image having it.

Empty string equals NULL. Oracle stores '' as NULL. Validation rules like nullable and required diverge from MySQL expectations, ->where('col', '') won't match what you think, and "not null" columns reject ''. Normalise at the application boundary.

Name-length limits bite late. Default object names cap at 30 characters. ORA_MAX_NAME_LEN=128 works only on 12.2+, and the table prefix counts toward the limit (a fix landed for that). Long migration-generated index names will fail at deploy time, not at development time.

Case sensitivity everywhere. Table/column names fold to uppercase unless quoted; string comparisons are case-sensitive. The oracle user provider solves auth specifically; for search use whereLike on 12cR2+ or explicit UPPER().

Don't combine row-limiting with locks. Offset pagination was fixed to be used only when no locks are present — if you combine paginate() with lockForUpdate(), verify the generated SQL on your version rather than assuming.

JSON updates are narrow by design. One path per JSON column per update(); 19c+ only; below that, no path updates at all. This matches Laravel's Postgres/MariaDB grammar behaviour, so it's a deliberate constraint, not a bug to work around.

CLOBs over 3999 characters need CLOB binding. upsert() handles it; hand-written inserts and DB::insert() with long strings may not. Validate payload sizes or bind explicitly.

Connection tuning keys are version-gated. retry_count, retry_delay, transport_connect_timeout require 12c+; expire_time requires 19c+. Setting them on 11g is silently pointless. For RAC/multi-host use a comma-separated DB_HOST with load_balance, or a hand-written tns descriptor — don't mix the two styles.

prefix vs prefix_schema. prefix prefixes table names; prefix_schema prefixes the schema owner. The README's dynamic-config example uses 'prefix' => 'schemaowner', which works but conflates the two — prefer the schema prefix for schema qualification.

Debugging approach that works. Log toSql() and bindings (Laravel's toRawSql() is more useful here), enable DB::listen() in local, and map QueryException codes: ORA-00001 (unique) surfaces as SQLSTATE 23000/code 1, ORA-02291 (FK) as 2291. Build your exception handler around those codes rather than message strings.

NLS and date formats. Session-level NLS_DATE_FORMAT affects implicit conversions. Prefer explicit TO_DATE/TO_CHAR in raw SQL and let whereDate do the conversion for you.

Extension points worth knowing. The dynamic config callback for per-user connections, a custom connection/grammar subclass if you need organisation-wide SQL tweaks, the bundled extension.neon for PHPStan, and per-connection server_version overrides for a mixed 11g/19c estate.

Upgrade deliberately. Read changelog/ before bumping: behaviour-changing fixes (pagination count alignment, whereDate compilation, hidden-column filtering, nullable preservation on column modify) can alter results or generated SQL without any API 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/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