PHPackages                             codyjheiser/laravel-db2-eloquent - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. codyjheiser/laravel-db2-eloquent

ActiveLibrary[Database &amp; ORM](/categories/database)

codyjheiser/laravel-db2-eloquent
================================

Laravel Eloquent extensions for IBM DB2 databases with column mapping, multi-column relationships, and auto-filtering

v1.3.2(2mo ago)192MITPHPPHP ^8.4CI passing

Since Dec 18Pushed 2mo agoCompare

[ Source](https://github.com/CodyJHeiser/laravel-db2-eloquent)[ Packagist](https://packagist.org/packages/codyjheiser/laravel-db2-eloquent)[ RSS](/packages/codyjheiser-laravel-db2-eloquent/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (12)Versions (9)Used By (0)

Laravel DB2 Eloquent
====================

[](#laravel-db2-eloquent)

[![Latest Version on Packagist](https://camo.githubusercontent.com/6bd8fea234bb25aa31d735b8aee3c3a46ba268a36cb80a29358bef3992708300/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f636f64796a6865697365722f6c61726176656c2d6462322d656c6f7175656e742e737667)](https://packagist.org/packages/codyjheiser/laravel-db2-eloquent)

Laravel Eloquent extensions for IBM DB2 databases with column mapping, multi-column relationships, and auto-filtering.

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Generating Models](#generating-models)
- [Database Schema](#database-schema)
    - [Testing Database](#testing-database)
- [Column Mapping](#column-mapping)
    - [Casts](#casts)
    - [IBM Date Cast](#ibm-date-cast)
    - [IBM Time Cast](#ibm-time-cast)
    - [IBM DateTime Cast](#ibm-datetime-cast)
- [Automatic Filtering](#automatic-filtering)
- [Multi-Column Relationships](#multi-column-relationships)
- [Union ALL (Combined Tables)](#union-all-combined-tables)
- [Extensions (Joined Tables)](#extensions-joined-tables)
- [Query Logging](#query-logging)
- [Helper Methods](#helper-methods)
- [Configuration](#configuration)
- [Development](#development)
- [License](#license)

Installation
------------

[](#installation)

```
composer require codyjheiser/laravel-db2-eloquent
```

### Requirements

[](#requirements)

- PHP 8.4+
- Laravel 12+

Basic Usage
-----------

[](#basic-usage)

```
use CodyJHeiser\Db2Eloquent\Model;

class Customer extends Model
{
    protected $table = 'VARCUST';
    protected string $schema = 'R60FILES';

    protected array $maps = [
        'RMCUST' => 'customer_number',
        'RMNAME' => 'name',
        'RMADD1' => 'address',
        'RMDEL'  => 'delete_code',
        'RMCMP'  => 'company_number',
    ];
}

// Query with human-readable column names
Customer::where('customer_number', '123')->first();
Item::where('item_number', 'ABC123')->orderBy('manufacturer')->get();
```

Generating Models
-----------------

[](#generating-models)

Use the Artisan command to quickly scaffold new DB2 models:

```
# Basic usage - creates app/Models/IBM/Customer.php
php artisan make:db2-model Customer VARCUST

# With schema prefix
php artisan make:db2-model Customer R60FILES.VARCUST

# Custom path - creates app/Models/Customer.php
php artisan make:db2-model Customer VARCUST --path=Models
```

The generated model includes the schema, table, casts, and maps properties ready to be filled in.

Database Schema
---------------

[](#database-schema)

Models use a `$schema` property to define the database schema prefix, keeping table definitions DRY.

```
class Item extends Model
{
    protected string $schema = 'R60FILES';
    protected $table = 'VINITEM';    // Results in: R60FILES.VINITEM
}

class CommissionPricing extends Model
{
    protected string $schema = 'R60FSDTA';
    protected $table = 'FSCOMPBT';   // Results in: R60FSDTA.FSCOMPBT
}
```

If your table already includes the schema prefix (e.g., `R60FILES.VINITEM`), it will be used as-is.

### Testing Database

[](#testing-database)

Use the `testing()` scope to query the testing database instead of production. By default, this swaps `R` to `T` in the schema prefix:

```
// Production (default)
Customer::first();              // R60FILES.VARCUST
CommissionPricing::first();     // R60FSDTA.FSCOMPBT

// Testing database
Customer::testing()->first();              // T60FILES.VARCUST
CommissionPricing::testing()->first();     // T60FSDTA.FSCOMPBT
```

#### Custom Schema Configuration

[](#custom-schema-configuration)

You can customize how test schemas are determined:

```
class MyModel extends Model
{
    // Option 1: Explicit test schema
    protected string $schema = 'PROD_SCHEMA';
    protected ?string $testSchema = 'TEST_SCHEMA';

    // Option 2: Prefix replacement (default behavior)
    protected string $schema = 'R60FILES';
    protected string $schemaPrefixProd = 'R';   // Default: 'R'
    protected string $schemaPrefixTest = 'T';   // Default: 'T'
}
```

**Important:** When using `testing()` with `withExtensions()`, you must call `testing()` FIRST:

```
// Correct - testing() before withExtensions()
Customer::testing()->withExtensions()->first();

// WRONG - extension tables will still use production schema
Customer::withExtensions()->testing()->first();
```

Column Mapping
--------------

[](#column-mapping)

Define `$maps` to translate DB columns to human-readable names:

```
class Customer extends Model
{
    protected string $schema = 'R60FILES';
    protected $table = 'VARCUST';

    protected array $maps = [
        'RMDEL' => 'delete_code',
        'RMCMP' => 'company_number',
        'RMCUST' => 'customer_number',
        'RMNAME' => 'name',
        'RMADD1' => 'address',
    ];
}
```

- Queries use mapped names: `where('customer_number', '123')`
- Output uses mapped names: `{ "customer_number": "123", "name": "Acme" }`
- Access properties with mapped names: `$customer->customer_number`

### Auto-Select Mapped Columns

[](#auto-select-mapped-columns)

By default, queries automatically select only columns defined in `$maps`. This prevents selecting unnecessary columns.

```
// These are equivalent - selectMapped is applied automatically
Customer::get();
Customer::selectMapped()->get();

// Also applies to eager-loaded relations automatically
Customer::with('orders')->get();
```

### Select All Columns

[](#select-all-columns)

```
// Bypass auto-select to get all columns
Customer::selectAll()->get();

// Or use explicit select()
Customer::select('*')->get();
Customer::select(['RMCUST', 'RMNAME', 'SOME_UNMAPPED_COL'])->get();
```

### Disable Auto-Select Per Model

[](#disable-auto-select-per-model)

```
protected bool $autoSelectMapped = false;
```

### Disable Auto-Mapping on Output

[](#disable-auto-mapping-on-output)

```
protected bool $applyMapsOnOutput = false;
```

### Casts

[](#casts)

Casts can use either **raw DB column names** or **mapped names**:

```
class CommissionPricing extends Model
{
    protected array $maps = [
        'PBCMP' => 'company_number',
        'PBPSLS' => 'pricing_sales',
    ];

    // Either style works
    protected $casts = [
        'PBCMP' => 'integer',           // Raw DB column name
        'pricing_sales' => 'decimal:2',  // Mapped name
    ];
}
```

The `company_number` column is auto-cast to integer for all IBM models.

### IBM Date Cast

[](#ibm-date-cast)

IBM stores dates as integers in `Ymd` format (e.g., `20251217`). Use the `IbmDate` cast to convert to Carbon or a formatted string:

```
use CodyJHeiser\Db2Eloquent\Casts\IbmDate;
use CodyJHeiser\Db2Eloquent\Casts\IbmDateNullable;

protected $casts = [
    // Returns Carbon instance
    'SHSCDT' => IbmDate::class,

    // Returns formatted string
    'SHCLDT' => IbmDate::class.':date',      // "2025-12-17"
    'SHCLDT' => IbmDate::class.':Y-m-d',     // "2025-12-17"
    'SHCLDT' => IbmDate::class.':m/d/Y',     // "12/17/2025"
    'SHCLDT' => IbmDate::class.':us',        // "12/17/2025"
    'SHCLDT' => IbmDate::class.':eu',        // "17/12/2025"

    // Handles special values like 99999999 as null
    'PBTODT' => IbmDateNullable::class,
];
```

**Format shortcuts:**

- `:date` → `Y-m-d`
- `:us` → `m/d/Y`
- `:eu` → `d/m/Y`
- Or use any custom PHP date format string

**Carbon instance (no format):**

```
$call->scheduled_date;                    // Carbon instance
$call->scheduled_date->format('m/d/Y');   // "12/17/2025"
$call->scheduled_date->diffForHumans();   // "2 days ago"

// Setting accepts Carbon, string, or integer
$call->scheduled_date = Carbon::now();
$call->scheduled_date = '2025-12-25';
$call->scheduled_date = 20251225;
```

### IBM Time Cast

[](#ibm-time-cast)

IBM stores times as integers in `Hms` format (e.g., `73733` = `07:37:33`). Use the `IbmTime` cast to convert to Carbon or a formatted string:

```
use CodyJHeiser\Db2Eloquent\Casts\IbmTime;

protected $casts = [
    // Returns Carbon instance
    'SHTMCD' => IbmTime::class,

    // Returns formatted string
    'SHTMCD' => IbmTime::class.':time',       // "07:37:33"
    'SHTMCD' => IbmTime::class.':H:i:s',      // "07:37:33"
    'SHTMCD' => IbmTime::class.':12h',         // "7:37:33 AM"
    'SHTMCD' => IbmTime::class.':short',       // "07:37"
];
```

**Format shortcuts:**

- `:time` → `H:i:s`
- `:12h` → `g:i:s A`
- `:short` → `H:i`
- Or use any custom PHP date format string

**Carbon instance (no format):**

```
$call->scheduled_time;                    // Carbon instance
$call->scheduled_time->format('g:i A');   // "7:37 AM"

// Setting accepts Carbon, string, or integer
$call->scheduled_time = Carbon::createFromTime(14, 30, 0);
$call->scheduled_time = '14:30:52';
$call->scheduled_time = 143052;
```

### IBM DateTime Cast

[](#ibm-datetime-cast)

Combine date and time into a single Carbon instance. Works with a single datetime column or two separate date + time columns:

```
use CodyJHeiser\Db2Eloquent\Casts\IbmDateTime;

protected $casts = [
    // Single datetime column (e.g., 20251217143052)
    'SHDTTM' => IbmDateTime::class,

    // Two columns: date (Ymd) + time (Hms)
    'SHDATE,SHTIME' => IbmDateTime::class,

    // With mapped column names
    'scheduled_date,scheduled_time' => IbmDateTime::class,

    // With output format
    'SHDATE,SHTIME' => IbmDateTime::class.':datetime',     // "2025-12-17 14:30:52"
    'SHDATE,SHTIME' => IbmDateTime::class.':Y-m-d H:i:s',  // "2025-12-17 14:30:52"
    'SHDATE,SHTIME' => IbmDateTime::class.':us',            // "12/17/2025 2:30:52 PM"
];
```

**Format shortcuts:**

- `:datetime` → `Y-m-d H:i:s`
- `:date` → `Y-m-d`
- `:us` → `m/d/Y g:i:s A`
- `:eu` → `d/m/Y H:i:s`
- Or use any custom PHP date format string

**Two-column access:**

```
// Access via the first column name (or its mapped name)
$schedule->SHDATE;           // Carbon instance with date + time combined
$schedule->scheduled_date;   // Same Carbon instance

// Setting updates both columns
$schedule->scheduled_date = Carbon::now();
$schedule->scheduled_date = '2025-12-17 14:30:52';

// toArray() includes the combined value under the first column's mapped name
$schedule->toArray();  // ['scheduled_date' => Carbon(...), ...]
```

**Custom input formats:**

```
// Specify custom date/time input formats (default: Ymd, His)
'SHDATE,SHTIME' => IbmDateTime::class.':Ymd,His',

// Custom input + output format
'SHDATE,SHTIME' => IbmDateTime::class.':Ymd,His,Y-m-d H:i:s',
```

### Duplicate Mapped Names

[](#duplicate-mapped-names)

When multiple DB columns map to the same name, **first wins**:

```
protected array $maps = [
    'RMCUST' => 'customer_number',  // This one wins
    'CXCUST' => 'customer_number',  // Skipped in output
];
```

Automatic Filtering
-------------------

[](#automatic-filtering)

By default, queries automatically filter by:

- `delete_code = 'A'` (active records only)
- `company_number = '1'` (default company)

These filters only apply if the model has `delete_code` and `company_number` mapped.

### Bypass Filters

[](#bypass-filters)

```
// Include inactive/deleted records
Item::withInactive()->get();

// Include all companies
Item::withAllCompanies()->get();

// Filter by a specific company
Item::forCompany('2')->get();

// Remove all automatic filters
Item::unfiltered()->get();

// Combine bypasses
Item::withInactive()->withAllCompanies()->get();
```

### Disable Per Model

[](#disable-per-model)

```
class SomeModel extends Model
{
    // Disable automatic filtering
    protected bool $filterActiveOnly = false;
    protected bool $filterByCompany = false;

    // Or change the default values
    protected string $activeDeleteCode = 'I';   // Different "active" code
    protected string $defaultCompany = '2';     // Different default company
}
```

Multi-Column Relationships
--------------------------

[](#multi-column-relationships)

IBM/DB2 tables often use composite keys. Use array syntax for multi-column relationships.

When both models have the same mapped column names, you only need to specify them once:

```
class ItemBalance extends Model
{
    protected array $maps = [
        'IFCOMP' => 'company_number',
        'IFITEM' => 'item_number',
    ];

    public function item()
    {
        // If both models map to 'company_number' and 'item_number'
        return $this->belongsTo(Item::class, ['company_number', 'item_number']);

        // Single column works too
        // return $this->belongsTo(Item::class, 'item_number');

        // Or specify both sides explicitly
        // return $this->belongsTo(Item::class, ['company_number', 'item_number'], ['company_number', 'item_number']);

        // Raw DB columns still work
        // return $this->belongsTo(Item::class, ['IFCOMP', 'IFITEM'], ['ICCMP', 'ICITEM']);
    }
}

class Item extends Model
{
    public function balances()
    {
        return $this->hasMany(ItemBalance::class, ['company_number', 'item_number']);
    }

    public function primaryBalance()
    {
        return $this->hasOne(ItemBalance::class, ['company_number', 'item_number']);
    }
}
```

Supported relationship types:

- `belongsTo($related, $foreignKey)` - ownerKey defaults to same as foreignKey
- `hasMany($related, $foreignKey)` - localKey defaults to same as foreignKey
- `hasOne($related, $foreignKey)` - localKey defaults to same as foreignKey

Column names are automatically translated to DB columns using each model's `$maps`. The trait generates DB2-compatible SQL using `AND`/`OR` conditions instead of tuple `IN` syntax.

Union ALL (Combined Tables)
---------------------------

[](#union-all-combined-tables)

IBM DB2 tables often have a "live" and "history" version with identical columns (e.g., `SBSCHD` for live service calls and `SBHSHD` for history). The `HasUnionSources` trait lets you query both tables as one unified, read-only model via `UNION ALL`.

### Define a Base Union Model

[](#define-a-base-union-model)

The base class uses the trait and defines the shared columns, casts, and relationships. Only columns common to all source tables should be in `$maps`:

```
use CodyJHeiser\Db2Eloquent\Model;
use CodyJHeiser\Db2Eloquent\Concerns\HasUnionSources;

class ServiceCallAll extends Model
{
    use HasUnionSources;

    protected $connection = 'vai';
    protected string $schema = 'R60FILES';
    protected $table = 'SBSCHD';  // Fallback for connection/schema resolution

    protected array $unionSources = [
        ServiceCallLive::class,
        ServiceCallHistory::class,
    ];

    // Only shared columns — one table may have extra columns you don't need here
    protected array $maps = [
        'SHCMP'  => 'company_number',
        'SHCUST' => 'customer_number',
        'SHCLNO' => 'call_number',
        'SHCLST' => 'status',
        'SHCLDT' => 'call_date',
        'SHCITY' => 'city',
        'SHSTAT' => 'state',
    ];

    public function customer()
    {
        return $this->belongsTo(Customer::class, ['customer_number']);
    }
}
```

### Extend Into Individual Tables

[](#extend-into-individual-tables)

Child classes extend the base and override `$table`. They inherit all maps, casts, and relationships — no code duplication. The trait automatically detects child classes and skips all union/read-only behavior for them, so they work as normal models:

```
class ServiceCallLive extends ServiceCallAll
{
    protected $table = 'SBSCHD';

    // Optionally override $maps to add columns specific to this table
    // Optionally override $casts for table-specific cast behavior
}

class ServiceCallHistory extends ServiceCallAll
{
    protected $table = 'SBHSHD';
}
```

### Query the Union

[](#query-the-union)

The union model works like any other model for reads. All standard query methods work:

```
// Returns results from BOTH tables
ServiceCallAll::where('status', 'Q')->limit(10)->get();
ServiceCallAll::where('call_number', 12345)->first();
ServiceCallAll::exists();

// Auto-filtering works — applied inside each source query
ServiceCallAll::get();                    // Active records, company 1, from both tables
ServiceCallAll::unfiltered()->get();      // All records from both tables
ServiceCallAll::withInactive()->get();    // Include deleted records from both tables
```

### Source Identification

[](#source-identification)

Every row includes a `_source` column identifying which table it came from:

```
$record = ServiceCallAll::first();
$record->_source;  // "R60FILES.SBSCHD" or "R60FILES.SBHSHD"

$record->toArray();
// [
//     'call_number' => 5477830,
//     'customer_number' => '0499769',
//     'status' => 'S',
//     '_source' => 'R60FILES.SBSCHD',
//     ...
// ]
```

### Read-Only Protection

[](#read-only-protection)

The union model is read-only. All write operations throw `ReadOnlyModelException`:

```
use CodyJHeiser\Db2Eloquent\Exceptions\ReadOnlyModelException;

ServiceCallAll::query()->insert([...]);   // throws ReadOnlyModelException
ServiceCallAll::query()->update([...]);   // throws ReadOnlyModelException
$record->save();                          // throws ReadOnlyModelException
$record->delete();                        // throws ReadOnlyModelException
```

Child classes (Live, History) are **not** read-only — they work as normal models.

### How It Works

[](#how-it-works)

1. The trait rewrites `$maps` to identity mappings (`call_number => call_number`) so the outer query operates on already-aliased names
2. Each source gets its own `SELECT DB_COL AS mapped_name FROM table` with auto-filters applied individually
3. Sources are joined with `UNION ALL` as a `FROM` subquery
4. A `_source` literal column is added to each source identifying the origin table
5. `isUnionModel()` uses reflection to detect whether the concrete class directly uses the trait — children that inherit it behave as normal models

Extensions (Joined Tables)
--------------------------

[](#extensions-joined-tables)

Define extension tables that join to the base table:

```
class Item extends Model
{
    protected string $schema = 'R60FILES';
    protected $table = 'VINITEM';

    protected array $extensions = [
        'R60FSDTA.VINITEMX' => [
            'join' => [
                'XICMP'  => 'ICCMP',   // ext.XICMP = base.ICCMP
                'XIITEM' => 'ICITEM',
            ],
            'columns' => ['*'],        // Optional: specific columns
            'maps' => [                // Optional: extension column maps
                'XITYPE' => 'type',
                'XIZUPID' => 'zuper_id',
            ],
        ],
    ];
}
```

**Note:** Extension table names include the schema prefix (e.g., `R60FSDTA.VINITEMX`). When using `testing()` scope, extension schemas are automatically converted (R→T).

### Query with Extensions

[](#query-with-extensions)

```
// Join all extensions
Item::withExtensions()->get();

// Join specific extension
Item::withExtension('R60FSDTA.VINITEMX')->get();

// Filter by extension record count
Item::whereHasExtension('R60FSDTA.VINITEMX')->get();           // has >= 1
Item::whereHasExtension('R60FSDTA.VINITEMX', '>', 1)->get();   // has > 1
Item::whereHasExtension('R60FSDTA.VINITEMX', '=', 0)->get();   // has none
Item::whereDoesntHaveExtension('R60FSDTA.VINITEMX')->get();    // shortcut for none

// Filter AND join in one call
Item::withWhereHasExtension('R60FSDTA.VINITEMX')->get();
Item::withWhereHasExtension('R60FSDTA.VINITEMX', '>', 1)->get();

// Combine with selectMapped - extension mapped columns auto-added
Item::selectMapped()->withExtensions()->get();
Item::withExtensions()->selectMapped()->get();
```

### Check Extension Records (Instance)

[](#check-extension-records-instance)

```
$item = Item::find(123);
$item->hasExtensionRecords('R60FSDTA.VINITEMX');        // true/false
$item->countExtensionRecords('R60FSDTA.VINITEMX');      // 0, 1, 2...
$item->hasMultipleExtensionRecords('R60FSDTA.VINITEMX'); // true if > 1
```

### Load Extension Separately

[](#load-extension-separately)

```
$item->loadExtension('R60FSDTA.VINITEMX');
$extData = $item->getExtensionData('R60FSDTA.VINITEMX');
```

Query Logging
-------------

[](#query-logging)

### Inline Logging (Recommended)

[](#inline-logging-recommended)

Enable logging for specific queries inline:

```
// Log to stderr (default)
Item::logQuery()->where('item_number', 'ABC')->first();

// Log to file
Item::logQuery('default')->selectMapped()->get();

// Log to both stderr and file
Item::logQuery(['stderr', 'default'])->withExtensions()->first();
```

### Global Logging

[](#global-logging)

Enable logging for all subsequent queries:

```
use CodyJHeiser\Db2Eloquent\Model;

// Enable via base model or any child model
Model::enableQueryLog();
Customer::enableQueryLog();

// Log to app log file instead of stderr
Customer::enableQueryLog(null);
Customer::enableQueryLog('default');

// Log to multiple channels
Customer::enableQueryLog(['stderr', 'default']);

// Run queries...
Customer::where('customer_number', '123')->first();

// Manage logs
Customer::dumpQueryLog();        // Dump to output
Customer::getQueryLog();         // Get as array
Customer::clearQueryLog();       // Clear log
Customer::disableQueryLog();     // Turn off
```

**Log channels:**

- `'stderr'` (default) - outputs to console/terminal
- `null` or `'default'` - writes to Laravel's app log file
- `['stderr', 'default']` - logs to both

### Custom SQL Formatter

[](#custom-sql-formatter)

```
Model::setSqlFormatter(function ($sql, $bindings) {
    // Your custom formatting logic
    return $formattedSql;
});
```

Helper Methods
--------------

[](#helper-methods)

```
// Column mapping
$model->getMaps();                 // Base table maps
$model->getAllMaps();              // Base + extension maps
$model->getReverseMaps();          // Mapped name => DB column
$model->getDbColumn('name');       // 'name' => 'RMNAME'
$model->getMappedColumn('RMNAME'); // 'RMNAME' => 'name'

// Check definitions
$model->hasMaps();
$model->hasExtensions();

// Raw attributes (unmapped)
$model->getRawAttributes();
```

Configuration
-------------

[](#configuration)

### Connection

[](#connection)

By default, models use a connection named `db2`. Configure this in your `config/database.php`:

```
'connections' => [
    'db2' => [
        'driver' => 'db2',
        'host' => env('IBM_DB_HOST'),
        'port' => env('IBM_DB_PORT', 50000),
        'database' => env('IBM_DB_DATABASE'),
        'username' => env('IBM_DB_USERNAME'),
        'password' => env('IBM_DB_PASSWORD'),
        'schema' => env('IBM_DB_SCHEMA', 'QGPL'),
    ],
],
```

Override the connection in your model if needed:

```
protected $connection = 'my_other_db2_connection';
```

### Schema Configuration

[](#schema-configuration)

Models generated with `make:db2-model` pull their schema from config, allowing you to manage schemas centrally. Add this to your `config/database.php`:

```
'ibm' => [
    'schema' => [
        'R60FILES' => env('IBM_SCHEMA_FILES', 'R60FILES'),
        'R60FSDTA' => env('IBM_SCHEMA_FSDTA', 'R60FSDTA'),
    ],
],
```

Generated models use the config with a fallback to the default:

```
public function __construct(array $attributes = [])
{
    $this->schema = config('database.ibm.schema.R60FILES', 'R60FILES');

    parent::__construct($attributes);
}
```

This lets you switch schemas via environment variables without modifying model code.

### Default Company

[](#default-company)

Override the default company filter:

```
protected string $defaultCompany = '2';
```

### Disable Features

[](#disable-features)

```
// Disable auto-filtering by delete code
protected bool $filterActiveOnly = false;

// Disable auto-filtering by company
protected bool $filterByCompany = false;

// Disable auto-select of mapped columns
protected bool $autoSelectMapped = false;

// Disable output mapping
protected bool $applyMapsOnOutput = false;
```

Development
-----------

[](#development)

### Requirements

[](#requirements-1)

- PHP 8.4+
- Composer
- For integration tests: IBM i Access ODBC Driver + `pdo_odbc` PHP extension

### Setup

[](#setup)

```
# Clone the repository
git clone https://github.com/CodyJHeiser/laravel-db2-eloquent.git
cd laravel-db2-eloquent

# Install dependencies
composer install

# Copy environment file for integration testing (optional)
cp .env.example .env
# Edit .env with your DB2 credentials
```

### Testing

[](#testing)

The test suite has two types of tests:

**Unit Tests** - Run without a database (uses SQLite in-memory):

```
composer test
```

**Integration Tests** - Tests against a real DB2 connection:

```
# First configure .env with your DB2 credentials
composer test-integration
```

**All Tests**:

```
composer test-all
```

### Test Structure

[](#test-structure)

DirectoryPurposeDatabase Required`tests/Unit/`Pure logic tests (casts, mapping, traits)No (uses SQLite mock)`tests/Integration/`DB2-specific testsYes (real DB2)`tests/Feature/`End-to-end testsVariesUnit tests run on CI (GitHub Actions) without DB2. Integration tests are skipped unless DB2 credentials are configured.

### Environment Variables

[](#environment-variables)

For integration tests, create a `.env` file with:

```
IBM_DB_HOST=your-ibm-host
IBM_DB_PORT=50000
IBM_DB_DATABASE=your-database
IBM_DB_USERNAME=your-username
IBM_DB_PASSWORD=your-password
IBM_DB_SCHEMA=QGPL
```

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance86

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~18 days

Total

8

Last Release

70d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/3d17b8b27908188945d7e4a2ff8949e0a4eed753520545cacf7d76f1abe824be?d=identicon)[CodyJHeiser](/maintainers/CodyJHeiser)

---

Top Contributors

[![CodyJHeiser](https://avatars.githubusercontent.com/u/5769533?v=4)](https://github.com/CodyJHeiser "CodyJHeiser (14 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/codyjheiser-laravel-db2-eloquent/health.svg)

```
[![Health](https://phpackages.com/badges/codyjheiser-laravel-db2-eloquent/health.svg)](https://phpackages.com/packages/codyjheiser-laravel-db2-eloquent)
```

###  Alternatives

[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.6k29.9M146](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M132](/packages/laravel-pulse)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.2M25](/packages/yajra-laravel-oci8)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.5M2](/packages/glushkovds-phpclickhouse-laravel)[api-platform/laravel

API Platform support for Laravel

58171.5k14](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
