PHPackages                             timefrontiers/php-database-object - 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. timefrontiers/php-database-object

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

timefrontiers/php-database-object
=================================

SQL Database Object trait for Active Record pattern

v1.0.7(3w ago)0244MITPHPPHP &gt;=8.1

Since Apr 16Pushed 3w agoCompare

[ Source](https://github.com/timefrontiers/php-database-object)[ Packagist](https://packagist.org/packages/timefrontiers/php-database-object)[ RSS](/packages/timefrontiers-php-database-object/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (8)Dependencies (3)Versions (8)Used By (4)

TimeFrontiers PHP Database Object
=================================

[](#timefrontiers-php-database-object)

Database Object trait for Active Record pattern with query builder support.

[![PHP Version](https://camo.githubusercontent.com/04744bae0a61d2ffe29c26f07a9612eae20445fc6feaeb77b3af1f0e9be6447c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e312d3838393242462e737667)](https://php.net/)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)

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

[](#installation)

```
composer require timefrontiers/php-database-object
```

Requirements
------------

[](#requirements)

- PHP 8.1+
- `timefrontiers/php-sql-database` ^1.0
- `timefrontiers/php-has-errors` ^1.0

Quick Start
-----------

[](#quick-start)

```
use TimeFrontiers\Helper\DatabaseObject;
use TimeFrontiers\Helper\HasErrors;

class User {
  use DatabaseObject, HasErrors;

  protected static string $_db_name = 'myapp';
  protected static string $_table_name = 'users';
  protected static string $_primary_key = 'id';
  protected static array $_db_fields = []; // Auto-loaded from schema

  public int $id;
  public string $name;
  public string $email;
  public string $status = 'active';
  protected ?string $_created = null;
  protected ?string $_updated = null;
  protected ?string $_author = null;
}

// Find by ID
$user = User::findById(123);

// Query builder
$activeUsers = User::query()
  ->where('status', 'active')
  ->orderBy('name')
  ->limit(10)
  ->get();

// Create
$user = new User();
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->save();

// Update
$user->name = 'Jane Doe';
$user->save();

// Delete
$user->delete();
```

Connection Management
---------------------

[](#connection-management)

Three levels of connection resolution:

```
// 1. Instance-level (highest priority)
$user = new User();
$user->setConnection($conn);

// 2. Class-level
User::useConnection($conn);

// 3. Global fallback (lowest priority)
global $database;
$database = new SQLDatabase(...);
```

Required Static Properties
--------------------------

[](#required-static-properties)

```
class MyEntity {
  use DatabaseObject;

  // Required
  protected static string $_db_name = 'database_name';
  protected static string $_table_name = 'table_name';
  protected static string $_primary_key = 'id';

  // Optional (auto-loaded from INFORMATION_SCHEMA if empty)
  protected static array $_db_fields = [];
}
```

Query Builder
-------------

[](#query-builder)

### Basic Queries

[](#basic-queries)

```
// Find all
$users = User::findAll();

// Find by ID
$user = User::findById(123);

// Count
$count = User::countAll();

// Check existence
$exists = User::valueExists('email', 'john@example.com');
```

### Fluent Queries

[](#fluent-queries)

```
// WHERE conditions
User::query()
  ->where('status', 'active')           // status = 'active'
  ->where('age', '>', 18)               // age > 18
  ->where('role', '!=', 'admin')        // role != 'admin'
  ->get();

// OR conditions
User::query()
  ->where('status', 'active')
  ->orWhere('role', 'admin')
  ->get();

// IN / NOT IN
User::query()
  ->whereIn('status', ['active', 'pending'])
  ->whereNotIn('role', ['banned', 'suspended'])
  ->get();

// NULL checks
User::query()
  ->whereNull('deleted_at')
  ->whereNotNull('verified_at')
  ->get();

// Ordering
User::query()
  ->orderBy('name')
  ->orderByDesc('created_at')
  ->get();

// Pagination
User::query()
  ->limit(10)
  ->offset(20)
  ->get();

// Or use take()
User::query()->take(10, 20)->get();

// First result
$user = User::query()
  ->where('email', 'john@example.com')
  ->first();

// Count matching
$count = User::query()
  ->where('status', 'active')
  ->count();

// Check existence
$exists = User::query()
  ->where('email', 'john@example.com')
  ->exists();
```

### Custom SQL

[](#custom-sql)

Use placeholders for database/table names:

```
$users = User::findBySql(
  "SELECT * FROM :database:.:table:
   WHERE status = ? AND created_at > ?
   ORDER BY :primary_key: DESC",
  ['active', '2024-01-01']
);
```

PlaceholderReplaced With`:database:` or `:db:``$_db_name``:table:` or `:tbl:``$_table_name``:primary_key:` or `:pkey:``$_primary_key`CRUD Operations
---------------

[](#crud-operations)

### Create

[](#create)

```
$user = new User();
$user->name = 'John Doe';
$user->email = 'john@example.com';

if ($user->save()) {
  echo "Created with ID: {$user->id}";
} else {
  $errors = $user->getErrors();
}
```

### Update

[](#update)

```
$user = User::findById(123);
$user->name = 'Jane Doe';

if (!$user->save()) {
  $errors = $user->getErrors();
}
```

### Delete

[](#delete)

```
$user = User::findById(123);

if (!$user->delete()) {
  $errors = $user->getErrors();
}
```

Timestamps &amp; Author
-----------------------

[](#timestamps--author)

The trait automatically handles these fields if they exist:

PropertyBehavior`$_created`Set to current datetime on insert`$_updated`Set to current datetime on insert/update`$_author`Set from `$session->name` on insert```
class Post {
  use DatabaseObject;

  protected ?string $_created = null;
  protected ?string $_updated = null;
  protected ?string $_author = null;

  // Accessors
  public function created():?string { ... }  // Built-in
  public function updated():?string { ... }  // Built-in
  public function author():?string { ... }   // Built-in
}

$post = new Post();
$post->title = 'Hello';
$post->save();

echo $post->created();  // "2024-01-15 10:30:00"
echo $post->author();   // "john_doe" (from $session->name)
```

Empty Properties
----------------

[](#empty-properties)

By default, empty values are skipped during save. Use `$empty_props` to allow specific fields to be empty.

The trait declares `public array $empty_props = []` with an empty default. PHP 8 rejects a class-level redeclaration whose default differs from the trait's (`Fatal error: ... definition differs and is considered incompatible`), so consumers should not redeclare the property — assign the whitelist in the constructor instead:

```
class Article {
  use DatabaseObject;

  public string $title;
  public string $subtitle = '';              // Can be saved as empty string
  public ?string $meta_description = null;   // Can be saved as NULL

  public function __construct() {
    $this->empty_props = ['subtitle', 'meta_description'];
  }
}
```

Schema Caching
--------------

[](#schema-caching)

Field information is cached from `INFORMATION_SCHEMA` to avoid repeated queries:

```
use TimeFrontiers\Database\Schema\TableSchema;

// Clear cache for a specific table
TableSchema::clearCache('myapp', 'users');

// Clear cache for entire database
TableSchema::clearCache('myapp');

// Clear all cached schemas
TableSchema::clearCache();
```

Error Handling
--------------

[](#error-handling)

The trait uses `HasErrors` for error management:

```
$user = new User();
$user->email = 'invalid';

if (!$user->save()) {
  // Get all errors
  $errors = $user->getErrors();

  // Check specific context
  if ($user->hasErrors('_create')) {
    // Handle creation errors
  }

  // Get first error message
  $message = $user->firstError('_create');

  // Use with InstanceError for rank-based filtering
  $visibleErrors = (new InstanceError($user))->get('_create');
}
```

### Error Contexts

[](#error-contexts)

ContextTriggered By`_create`Insert failures`_update`Update failures`_delete`Delete failuresStatic Accessors
----------------

[](#static-accessors)

```
User::primaryKey();    // "id"
User::tableName();     // "users"
User::databaseName();  // "myapp"
User::tableFields();   // ["id", "name", "email", ...]
```

Complete Example
----------------

[](#complete-example)

```
use TimeFrontiers\Helper\DatabaseObject;
use TimeFrontiers\Helper\HasErrors;
use TimeFrontiers\SQLDatabase;

class Product {
  use DatabaseObject, HasErrors;

  protected static string $_db_name = 'store';
  protected static string $_table_name = 'products';
  protected static string $_primary_key = 'id';
  protected static array $_db_fields = [];

  public int $id;
  public string $name;
  public string $sku;
  public float $price;
  public int $stock = 0;
  public string $status = 'draft';
  protected ?string $_created = null;
  protected ?string $_updated = null;
  protected ?string $_author = null;

  public ?string $description = null;

  public function __construct() {
    $this->empty_props = ['description'];  // see "Empty Properties" above
  }

  /**
   * Publish the product.
   */
  public function publish():bool {
    if ($this->stock _userError('publish', 'Cannot publish product with no stock');
      return false;
    }

    $this->status = 'published';
    return $this->save();
  }

  /**
   * Find products low on stock.
   */
  public static function findLowStock(int $threshold = 10):array {
    return static::query()
      ->where('status', 'published')
      ->where('stock', '
