zero-to-prod/db-model

Generates PHP Artifacts to Represent Database Schemas

Maintainers

Package info

github.com/zero-to-prod/db-model

pkg:composer/zero-to-prod/db-model

Transparency log

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-08-10 14:54 UTC

This package is auto-updated.

Last update: 2026-08-10 14:57:29 UTC


README

Generates PHP artifacts to represent database schemas.

This is useful if you want a single source of truth for your database schema that you can reference in your codebase.

Install the package with db-model:install.

Scaffold PHP representation of your database tables with db-model:generate. Check them with db-model:check.

Requirements

Installation

composer require zero-to-prod/db-model

Configuration

CLI install:

php artisan db-model:install

Rerunning it is safe.

To publish the configuration file by itself instead:

php artisan vendor:publish --tag=db-model-config

namespace and path must describe the same place: a directory holding one subdirectory per schema, named after it.

'namespace' => 'App\\Sources\\Db',
'path' => app_path('Sources/Db'),

Usage

Scaffold a table enum beside it for every table in the database:

php artisan db-model:generate            # writes to the configured path
php artisan db-model:generate --dry-run  # reports what it would write
// app/Sources/Db/App/Users.php
namespace App\Sources\Db\App;

use ZeroToProd\DbModel\Column;
use ZeroToProd\DbModel\ColumnType;
use ZeroToProd\DbModel\HasColumnAttribute;
use ZeroToProd\DbModel\Table;

/**
 * @method string type()
 * @method string|null comment()
 * @method int|null length()
 * @method bool|null nullable()
 * @method bool|null unique()
 * @method bool|null primary_key()
 * @method bool|null auto_increment()
 */
#[Table(
    schema: App::class,
    attributes: [
        Table::name => 'users',
        Table::collate => 'utf8mb4_unicode_ci',
    ])]
enum Users: string
{
    use HasColumnAttribute;

    #[Column([
        Column::name => self::email,
        Column::comment => 'The users email',
        Column::type => ColumnType::varchar->value,
        Column::length => 255,
        Column::nullable => false,
        Column::unique => true,
    ])]
    case email = 'email';
}
Users::email->value;      // 'email'
Users::email->rules();    // ['required', 'string', 'max:255']
Users::email->type();     // 'varchar'
Users::email->length();   // 255
Users::email->unique();   // true
Users::email->comment();  // 'The users email'

rules() states only what the schema itself constrains. Uniqueness, confirmation and any format the database does not enforce belong to the request:

public function rules(): array
{
    return [
        'email' => [...Users::email->rules(), 'email', 'unique:users'],
        'password' => [...Users::password->rules(), 'confirmed'],
    ];
}
// config/db-model.php
'trait' => App\Sources\Db\HasColumn::class,
php artisan db-model:generate   # the enums now `use HasColumn`
Users::created_at->cast();   // 'immutable_datetime'
Users::created_at->rules();  // ['nullable', 'date'] — still from the package

Checking for drift

Add the check to your pipeline. It names every difference and exits non-zero:

php artisan db-model:check

Both commands take --schema to select the schema, defaulting to App, plus --connection and --database to say where to read it from. Without them the default connection and its own database are read:

php artisan db-model:check --schema=Reporting --connection=mysql --database=reporting
php artisan db-model:generate --schema=Reporting --database=reporting --path=/tmp/reporting

Agent development

The package registers an MCP server so coding agents can read how it is meant to be used. It requires laravel/mcp, and registers nothing without it.

composer require --dev laravel/mcp
php artisan mcp:start db-model

Register it with your agent:

claude mcp add db-model -- php artisan mcp:start db-model

Three tools are exposed:

  • readme — this document.
  • api — the exact signature of every public class, property and method. Anything unlisted is internal and may change in any release.
  • install — what db-model:install does, without a prompt to answer. Every argument defaults to the current setting, databases to the connection's own database. An unknown database name is answered with the list of real ones, and a config/db-model.php that says something else is left alone and reported until the call passes overwrite: true.
{
  "namespace": "App\\Sources\\Db",
  "path": "app/Sources/Db",
  "databases": ["app", "reporting"],
  "generate": true
}

Point the handle somewhere else, or turn the server off, in config/db-model.php:

'mcp' => [
    'enabled' => true,
    'handle' => 'db-model',
],

Development

composer check   # lint, rector, phpstan, 100% coverage, bc-check — mutates nothing
composer fix     # rector then pint
composer mcp list                      # the server's tools
composer mcp call api '{}'             # call one

composer check requires a coverage driver (Xdebug or pcov); without one Pest cannot satisfy the --min=100 gate.

The tests read a real MySQL schema. They create the database named by DB_DATABASE if it does not exist, and drop every table in it. Point them at a throwaway database — phpunit.xml defaults to testing_db_model on 127.0.0.1:3306:

DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=testing_db_model \
DB_USERNAME=sail DB_PASSWORD=password composer test

License

MIT. See LICENSE.