daycry/schemas

Database schema management, for CodeIgniter 4

v2.0.0 2025-09-30 11:04 UTC

This package is auto-updated.

Last update: 2025-09-30 16:48:36 UTC


README

Build status Coverage Status Downloads GitHub release (latest by date) GitHub stars GitHub license

Minimal core edition: focused only on drafting (database / model / directory), reading, and archiving (cache). Async, plugins, events, layered environments, complex validation, advanced relation flags, and export/import formats were removed for a lean runtime.

Quick Start

  1. Install with Composer: composer require daycry/schemas
  2. Generate and cache a schema (database + models) via spark: php spark schemas -draft database,model -archive cache
  3. Fetch it (try cache first, draft if missing):
    $schemas = service('schemas');
    $schema  = $schemas->load()->get(); // load() attempts cache (if archived previously)
    if (! $schema) {
         $schemas->draft()->archive('cache');
         $schema = $schemas->get();
    }

Core Feature Summary

  • Database structure introspection (tables, fields, indexes, foreign keys)
  • Draft sources: database, models, directory (user-provided PHP schema files)
  • Archive + read from cache (file/redis/etc via CI4 cache handlers)
  • Lightweight structure objects: Schema, Table, Field, Index, ForeignKey, Relation
  • Simple automation flags (draft / archive / read)
  • Explicit, extensible handler lists (swap or extend by config)

Removed Legacy Subsystems

Removed Feature Why Replacement / Path
Async background drafting Complexity > benefit Run synchronously (fast)
Plugin manager Indirection overhead Create small wrapper packages
Event bus Rare real use Add domain events externally if needed
Export/Import (json/php/yaml) Serialization bloat Future addon (see Extensions)
Snapshot/runtime overrides Hidden mutable state Explicit service instances
Layered environment profiles Hard to reason Single flat config class
Validation engine Redundant vs tests Rely on types + PHPUnit
Logging / metrics collector Unnecessary core weight Use app/logger directly
Advanced relation flags Too many toggles Single boolean $relationships

If you relied on something removed, create a thin external package that composes over this core.

Installation

Composer (recommended):

composer require daycry/schemas

Manual: clone/download and add the src namespace to your app/Config/Autoload.php.

Configuration

Publish (optional) configuration to your app (if a publisher command is present) or copy the distributed template to app/Config/Schemas.php.

Key options (see src/Config/Schemas.php):

public string $defaultGroup = 'default';        // Database group
public array  $ignoredTables = ['migrations'];  // Skip these tables
public array  $includedTables = [];             // If non-empty: only these tables
public string $tablePrefix = '';                // Add/remove prefix handling
public bool   $silent = true;                   // Best-effort mode
public array  $cache = [                        // Archive/read cache config
    'enabled' => false,
    'handler' => 'file',
    'ttl'     => 3600,
    'prefix'  => 'schemas_',
];
public bool $relationships = true;              // Auto relation detection
public array $automate = [                      // Auto actions when needed
    'draft' => true,
    'archive' => true,
    'read' => true,
];
public array $draftHandlers = [                 // Order-sensitive
    'database'  => DatabaseHandler::class,
    'model'     => ModelHandler::class,
    'directory' => DirectoryHandler::class,
];
public array $archiveHandlers = [               // Archive modes
    'cache' => CacheHandler::class,
];
public array $readHandlers = [                  // Reader sources
    'cache'     => \Daycry\Schemas\Reader\Handlers\CacheHandler::class,
    'directory' => \Daycry\Schemas\Reader\Handlers\DirectoryHandler::class,
    'php'       => \Daycry\Schemas\Reader\Handlers\PhpHandler::class,
    'json'      => \Daycry\Schemas\Reader\Handlers\JsonHandler::class,
];

Edit handler lists to extend or swap implementations.

Public API Surface

The intent is a small, explicit contract. Everything not listed here should be considered internal and subject to change between minor versions.

Service: Daycry\Schemas\Schemas

Workflow (chainable) methods:

  • draft(array|string|null $handlers = null): self – Merge drafted structures from one or more handler keys or class names. Null = all configured draft handlers in order.
  • archive(string|array $mode = 'cache'): self – Persist the current schema using configured archive handler(s) for a mode, or pass an array of archiver instances.
  • read(string|array $path): self – Load schema data from cache/directory/php/json sources and merge.

State helpers:

  • get(): ?Schema – Current in-memory schema (or null). Does NOT perform I/O.
  • load(): ?Schema – Attempt to load from cache if not already loaded; returns the schema or null.
  • setSchema(Schema $schema): self – Replace current schema.
  • reset(): self – Clear schema & errors.
  • getErrors(): string[] – Retrieve & clear collected errors.

Structures (Daycry\Schemas\Structures)

Plain data objects (all final except Mergeable base):

  • Schema, Table, Field, Index, ForeignKey, Relation, plus additional DB objects if present (e.g., Procedure, Trigger, View).

Draft Handlers (Daycry\Schemas\Drafter\Handlers)

  • DatabaseHandler – Introspects DB schema via configured database group.
  • ModelHandler – Parses application Models for table/field hints.
  • DirectoryHandler (+ sub-handlers like DirectoryHandlers\PhpHandler) – Loads user-provided schema PHP files.

Archive Handlers (Daycry\Schemas\Archiver\Handlers)

  • CacheHandler – Stores schema in CodeIgniter Cache.

Reader Handlers (Daycry\Schemas\Reader\Handlers)

  • CacheHandler, DirectoryHandler, PhpHandler, JsonHandler – Rehydrate schema from respective sources.

Extensibility Points

  • Add a new draft handler: implement DrafterInterface, register in $draftHandlers.
  • Add an archive handler: implement ArchiverInterface, add to $archiveHandlers list or new mode key.
  • Add a reader: implement ReaderInterface, map extension/key in $readHandlers.
  • Provide custom static schema slices: place PHP schema files in your configured schemasDirectory.

Usage

Basic automated workflow (all automate flags true):

$schemas = service('schemas');
$schema = $schemas->get(); // Will auto draft/read/archive on first call depending on flags

Manual workflow control:

$schemas = service('schemas');

// Draft database + models then archive
$schemas->draft(['database','model'])->archive();

// Later, read from cache, add directory schemas, and fetch
$schema = $schemas->read('cache')->draft('directory')->get();

Custom handler instance:

$db = db_connect('alternate');
$schemas = service('schemas');
$databaseHandler = new \Daycry\Schemas\Drafter\Handlers\DatabaseHandler(config('Schemas'), $db);
$schema = $schemas->draft([$databaseHandler])->get();

Command

Spark command to draft & archive or print schemas:

php spark schemas -draft database,model -archive cache
php spark schemas -draft database,model,directory -print

Flags:

  • -draft handler1,handler2 (optional; default = all configured)
  • -archive cache (or omit to skip archiving)
  • -print output current schema to console (bypasses archive)

Automation

$automate can be used (if enabled) to mimic legacy behavior (auto draft/read/archive). In the minimal core you are encouraged to call the methods you need explicitly. The provided load() method offers a lightweight manual cache check pattern. Disable flags you do not want executed implicitly.

Recommended explicit flow:

$schemas = service('schemas');
if (! $schemas->load()) {            // null => nothing in cache
    $schemas->draft()->archive();    // build & store
}
$schema = $schemas->get();           // schema now present

Relationships, Foreign Keys & Lazy Tables

Drafting from the database collects tables, fields, indexes and foreign keys. Relationships are inferred (when $relationships = true) using:

  • Real foreign keys (preferred)
  • Pivot table heuristics: table with only two foreign key columns referencing distinct tables → many-to-many
  • Column naming pattern: {other_table}_id → belongsTo/hasMany guess (fallback)

Lazy Table Placeholders (Cache)

When archiving to cache, the CacheHandler stores a scaffold: each table slot becomes tableName => true and the full Table object is saved under a separate cache key. This minimizes the size of the main schema entry and defers hydration.

How to materialize:

$schemas->load();              // scaffold only (tables => true)
$schema = $schemas->get();

// Access one table (lazy load via reader magic)
$users = $schema->tables->users; // now users is a Table object

// Or fetch all tables eagerly if reader supports it
if (is_object($schema->tables) && method_exists($schema->tables, 'fetchAll')) {
    $schema->tables->fetchAll();
}

Foreign Keys of a Table

$posts = $schema->tables->posts;         // lazy hydrate
foreach ($posts->foreignKeys as $name => $fk) {
    echo $fk->column_name, ' -> ', $fk->foreign_table_name, '.', $fk->foreign_column_name, PHP_EOL;
}

If a table still shows as true, call:

$schema->tables->fetch('posts');

Intervention / Manual Adjustments

Supported Draft / Archive / Read

Draft: database, model, directory

Archive: cache

Read: cache, directory, php, json

Database Support

All CodeIgniter 4 database drivers work but due to some differences in index handling they may not all report the same results. Example: see skipped tests for SQLite3.

Extensions & Addons

You can prototype external addons without modifying core by creating packages that:

  • Provide new handler classes (implement the appropriate interface)
  • Recommend a config snippet for users to append handlers
  • (Optionally) add a spark command for export or diagnostics

Example add‑ons (future packages):

  • daycry/schemas-export-json – export/import JSON
  • daycry/schemas-events – lightweight event dispatcher wrapper

Roadmap (Minimal Core Perspective)

Potential future (only if demanded by real-world use):

  • External export/import addon(s)
  • Optional tiny event hook layer
  • Migration diff generation as standalone tool

PRs welcome – keep the core surface area minimal.