{"id":14063314,"url":"https://github.com/butschster/dbml-parser","last_synced_at":"2026-01-12T08:33:36.001Z","repository":{"id":40692993,"uuid":"381038225","full_name":"butschster/dbml-parser","owner":"butschster","description":"DBML parser for PHP8. It's a PHP parser for DBML syntax.","archived":false,"fork":false,"pushed_at":"2024-09-23T11:43:18.000Z","size":107,"stargazers_count":62,"open_issues_count":3,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-10-04T19:12:43.640Z","etag":null,"topics":["dbml","parser","parser-library","php","php8"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/butschster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-28T13:17:39.000Z","updated_at":"2025-10-04T14:25:04.000Z","dependencies_parsed_at":"2024-11-22T08:33:40.131Z","dependency_job_id":null,"html_url":"https://github.com/butschster/dbml-parser","commit_stats":{"total_commits":33,"total_committers":3,"mean_commits":11.0,"dds":0.4545454545454546,"last_synced_commit":"1d3342db40138ddcefb99d98a9ce59c9a56cdd2c"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/butschster/dbml-parser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/butschster%2Fdbml-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/butschster%2Fdbml-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/butschster%2Fdbml-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/butschster%2Fdbml-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/butschster","download_url":"https://codeload.github.com/butschster/dbml-parser/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/butschster%2Fdbml-parser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28337599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dbml","parser","parser-library","php","php8"],"created_at":"2024-08-13T07:03:16.071Z","updated_at":"2026-01-12T08:33:35.992Z","avatar_url":"https://github.com/butschster.png","language":"PHP","readme":"# DBML Parser for PHP\n\n[![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dbutschster%26type%3Dpatrons\u0026style=flat)](https://patreon.com/butschster)\n[![Latest Stable Version](https://poser.pugx.org/butschster/dbml-parser/v/stable)](https://packagist.org/packages/butschster/dbml-parser)\n[![Build Status](https://github.com/butschster/dbml-parser/actions/workflows/tests.yml/badge.svg)](https://github.com/butschster/dbml-parser/actions/workflows/tests.yml)\n[![Total Downloads](https://poser.pugx.org/butschster/dbml-parser/downloads)](https://packagist.org/packages/butschster/dbml-parser)\n[![License](https://poser.pugx.org/butschster/dbml-parser/license)](https://packagist.org/packages/butschster/dbml-parser)\n\n![DBML-parser](https://user-images.githubusercontent.com/773481/125667174-8b349bc0-fb5f-49a2-a651-1cac06bba151.jpg)\n\nA production-ready PHP 8.3+ parser for [DBML (Database Markup Language)](https://www.dbml.org/), transforming\nhuman-readable database schemas into structured PHP objects. Generate migrations, ORM entities, documentation, or\nvisualization tools from a single DBML source.\n\n## Table of Contents\n\n- [Why DBML Parser?](#why-dbml-parser)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n- [Complete API Reference](#complete-api-reference)\n    - [Schema Operations](#schema-operations)\n    - [Project Definition](#project-definition)\n    - [Table Operations](#table-operations)\n    - [Column Properties](#column-properties)\n    - [Index Configuration](#index-configuration)\n    - [Enum Management](#enum-management)\n    - [Table Groups](#table-groups)\n    - [Relationships (Refs)](#relationships-refs)\n- [Advanced Usage](#advanced-usage)\n- [Use Cases](#use-cases)\n- [Error Handling](#error-handling)\n- [Contributing](#contributing)\n- [Credits](#credits)\n\n## Why DBML Parser?\n\nDBML (Database Markup Language) is a simple, readable DSL for defining database structures. This parser enables you to:\n\n- **Version Control Database Schemas**: Store your database design in git-friendly text format\n- **Generate Code Automatically**: Create ORM entities, migrations, models, and documentation from DBML\n- **Visualize Database Design**: Build interactive schema diagrams and documentation sites\n- **Share Database Specs**: Communicate database structure with teams using human-readable syntax\n- **Build Schema Tools**: Create custom tools for schema validation, transformation, or analysis\n\nThis library was inspired by [dbdiagram.io](https://dbdiagram.io/) and built using the\npowerful [phplrt](https://phplrt.org) parser toolkit.\n\n## Installation\n\n**Requirements:**\n\n- PHP 8.3 or higher\n- Composer\n\nInstall via Composer:\n\n```bash\ncomposer require butschster/dbml-parser\n```\n\n## Quick Start\n\nParse a DBML schema and access its components:\n\n```php\nuse Butschster\\Dbml\\DbmlParserFactory;\n\n// Create parser instance\n$parser = DbmlParserFactory::create();\n\n// Parse DBML string\n$schema = $parser-\u003eparse(\u003c\u003c\u003cDBML\n    Project ecommerce {\n        database_type: 'PostgreSQL'\n        Note: 'E-commerce database schema'\n    }\n    \n    Table users {\n        id int [pk, increment]\n        email varchar(255) [unique, not null]\n        created_at timestamp [default: `now()`]\n    }\n    \n    Table orders {\n        id int [pk, increment]\n        user_id int [not null, ref: \u003e users.id]\n        status order_status\n        total decimal(10,2)\n    }\n    \n    Enum order_status {\n        pending\n        processing\n        shipped\n        delivered\n    }\nDBML);\n\n// Access schema components\nforeach ($schema-\u003egetTables() as $table) {\n    echo \"Table: {$table-\u003egetName()}\\n\";\n    \n    foreach ($table-\u003egetColumns() as $column) {\n        echo \"  - {$column-\u003egetName()}: {$column-\u003egetType()-\u003egetName()}\\n\";\n    }\n}\n```\n\n## Core Concepts\n\nThe parser transforms DBML into an Abstract Syntax Tree (AST) with these key node types:\n\n| Node Type        | Purpose                          | Example                             |\n|------------------|----------------------------------|-------------------------------------|\n| `SchemaNode`     | Root container for entire schema | Top-level access to all components  |\n| `ProjectNode`    | Project metadata and settings    | Database type, version, notes       |\n| `TableNode`      | Table definition with columns    | `Table users { ... }`               |\n| `ColumnNode`     | Column with type and constraints | `id int [pk, not null]`             |\n| `IndexNode`      | Single or composite index        | `Indexes { (col1, col2) [unique] }` |\n| `EnumNode`       | Enum type definition             | `Enum status { active, inactive }`  |\n| `RefNode`        | Foreign key relationship         | `Ref: orders.user_id \u003e users.id`    |\n| `TableGroupNode` | Logical grouping of tables       | `TableGroup core { users, roles }`  |\n\n## Complete API Reference\n\n### Schema Operations\n\nThe `SchemaNode` is your entry point for accessing all parsed components.\n\n#### Get All Tables\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\SchemaNode $schema */\n\n// Get all tables as array\n$tables = $schema-\u003egetTables();\n// Returns: TableNode[]\n\nforeach ($tables as $table) {\n    echo $table-\u003egetName() . \"\\n\";\n}\n```\n\n#### Get Specific Table\n\n```php\n// Check if table exists\nif ($schema-\u003ehasTable('users')) {\n    $table = $schema-\u003egetTable('users');\n    // Returns: TableNode\n}\n\n// Throws TableNotFoundException if not found\ntry {\n    $table = $schema-\u003egetTable('nonexistent');\n} catch (\\Butschster\\Dbml\\Exceptions\\TableNotFoundException $e) {\n    // Handle missing table\n}\n```\n\n#### Access Project Metadata\n\n```php\n// Check if project is defined\nif ($schema-\u003ehasProject()) {\n    $project = $schema-\u003egetProject();\n    // Returns: ProjectNode|null\n}\n```\n\n#### Get Table Groups\n\n```php\n// Get all table groups\n$groups = $schema-\u003egetTableGroups();\n// Returns: TableGroupNode[]\n\n// Check specific group\nif ($schema-\u003ehasTableGroup('core_tables')) {\n    $group = $schema-\u003egetTableGroup('core_tables');\n    // Returns: TableGroupNode\n}\n\n// Throws TableGroupNotFoundException if not found\n```\n\n#### Get Enums\n\n```php\n// Get all enums\n$enums = $schema-\u003egetEnums();\n// Returns: EnumNode[]\n\n// Access specific enum\nif ($schema-\u003ehasEnum('user_status')) {\n    $enum = $schema-\u003egetEnum('user_status');\n    // Returns: EnumNode\n}\n\n// Throws EnumNotFoundException if not found\n```\n\n#### Get Relationships\n\n```php\n// Get all foreign key relationships\n$refs = $schema-\u003egetRefs();\n// Returns: RefNode[]\n\nforeach ($refs as $ref) {\n    $leftTable = $ref-\u003egetLeftTable()-\u003egetTable();\n    $rightTable = $ref-\u003egetRightTable()-\u003egetTable();\n    echo \"{$leftTable} references {$rightTable}\\n\";\n}\n```\n\n**Complete Schema Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\SchemaNode;\n\n/** @var SchemaNode $schema */\n\n// Project information\n$project = $schema-\u003egetProject();\n$dbType = $project?-\u003egetSetting('database_type')-\u003egetValue();\n\n// All components\n$allTables = $schema-\u003egetTables();        // All table definitions\n$allEnums = $schema-\u003egetEnums();          // All enum types\n$allRefs = $schema-\u003egetRefs();            // All relationships\n$allGroups = $schema-\u003egetTableGroups();   // All table groups\n\n// Component counts\n$tableCount = count($allTables);\n$enumCount = count($allEnums);\n$refCount = count($allRefs);\n\necho \"Database: {$dbType}, Tables: {$tableCount}, Enums: {$enumCount}\\n\";\n```\n\n### Project Definition\n\nThe `ProjectNode` stores database-level metadata and configuration.\n\n**DBML Syntax:**\n\n```dbml\nProject my_app {\n    database_type: 'PostgreSQL'\n    note: 'Application database schema'\n}\n```\n\n#### Access Project Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\ProjectNode $project */\n$project = $schema-\u003egetProject();\n\n// Get project name\n$name = $project-\u003egetName();\n// Returns: string (e.g., 'my_app')\n\n// Get project note\n$note = $project-\u003egetNote();\n// Returns: string|null\n\n// Get all settings\n$settings = $project-\u003egetSettings();\n// Returns: SettingNode[]\n```\n\n#### Access Project Settings\n\n```php\n// Check if setting exists\nif ($project-\u003ehasSetting('database_type')) {\n    $setting = $project-\u003egetSetting('database_type');\n    // Returns: SettingNode\n    \n    $key = $setting-\u003egetKey();     // 'database_type'\n    $value = $setting-\u003egetValue();  // 'PostgreSQL'\n}\n\n// Throws ProjectSettingNotFoundException if not found\ntry {\n    $setting = $project-\u003egetSetting('unknown_setting');\n} catch (\\Butschster\\Dbml\\Exceptions\\ProjectSettingNotFoundException $e) {\n    // Handle missing setting\n}\n```\n\n#### Get Node Position\n\n```php\n// Get offset in source DBML for debugging\n$offset = $project-\u003egetOffset();\n// Returns: int (character position in parsed string)\n```\n\n**Complete Project Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\ProjectNode;\n\n/** @var ProjectNode $project */\n\necho \"Project: {$project-\u003egetName()}\\n\";\necho \"Note: {$project-\u003egetNote()}\\n\\n\";\n\necho \"Settings:\\n\";\nforeach ($project-\u003egetSettings() as $setting) {\n    echo \"  {$setting-\u003egetKey()}: {$setting-\u003egetValue()}\\n\";\n}\n\n// Common settings to check\n$dbType = $project-\u003ehasSetting('database_type') \n    ? $project-\u003egetSetting('database_type')-\u003egetValue() \n    : 'unknown';\n\n$version = $project-\u003ehasSetting('version')\n    ? $project-\u003egetSetting('version')-\u003egetValue()\n    : null;\n```\n\n### Table Operations\n\nThe `TableNode` represents a database table with columns, indexes, and relationships.\n\n**DBML Syntax:**\n\n```dbml\nTable users as U {\n    id int [pk, increment]\n    email varchar(255) [unique, not null]\n    created_at timestamp\n    \n    Note: 'User accounts'\n    \n    Indexes {\n        email [unique]\n        (email, created_at) [name: 'email_created_idx']\n    }\n}\n```\n\n#### Access Table Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\TableNode $table */\n$table = $schema-\u003egetTable('users');\n\n// Table name\n$name = $table-\u003egetName();\n// Returns: string (e.g., 'users')\n\n// Table alias (from \"as U\")\n$alias = $table-\u003egetAlias();\n// Returns: string|null (e.g., 'U')\n\n// Table note\n$note = $table-\u003egetNote();\n// Returns: string|null\n\n// Source position\n$offset = $table-\u003egetOffset();\n// Returns: int\n```\n\n#### Get Table Columns\n\n```php\n// Get all columns\n$columns = $table-\u003egetColumns();\n// Returns: ColumnNode[] (associative array keyed by column name)\n\nforeach ($columns as $columnName =\u003e $column) {\n    echo \"{$columnName}: {$column-\u003egetType()-\u003egetName()}\\n\";\n}\n\n// Check if column exists\nif ($table-\u003ehasColumn('email')) {\n    $column = $table-\u003egetColumn('email');\n    // Returns: ColumnNode\n}\n\n// Throws ColumnNotFoundException if not found\ntry {\n    $column = $table-\u003egetColumn('nonexistent');\n} catch (\\Butschster\\Dbml\\Exceptions\\ColumnNotFoundException $e) {\n    // Handle missing column\n}\n```\n\n#### Get Table Indexes\n\n```php\n// Get all indexes\n$indexes = $table-\u003egetIndexes();\n// Returns: IndexNode[]\n\nforeach ($indexes as $index) {\n    $columns = $index-\u003egetColumns();\n    $indexName = $index-\u003egetName();\n    $isPrimary = $index-\u003eisPrimaryKey();\n    $isUnique = $index-\u003eisUnique();\n}\n```\n\n**Complete Table Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\TableNode;\n\n/** @var TableNode $table */\n\necho \"Table: {$table-\u003egetName()}\";\nif ($alias = $table-\u003egetAlias()) {\n    echo \" (alias: {$alias})\";\n}\necho \"\\n\";\n\nif ($note = $table-\u003egetNote()) {\n    echo \"Note: {$note}\\n\";\n}\n\necho \"\\nColumns:\\n\";\nforeach ($table-\u003egetColumns() as $column) {\n    $type = $column-\u003egetType()-\u003egetName();\n    $size = $column-\u003egetType()-\u003egetSize();\n    $nullable = $column-\u003eisNull() ? 'NULL' : 'NOT NULL';\n    $pk = $column-\u003eisPrimaryKey() ? '[PK]' : '';\n    \n    echo \"  {$column-\u003egetName()} {$type}\";\n    if ($size) {echo \"({$size})\";}\n    echo \" {$nullable} {$pk}\\n\";\n}\n\necho \"\\nIndexes:\\n\";\nforeach ($table-\u003egetIndexes() as $index) {\n    $indexType = $index-\u003eisPrimaryKey() ? 'PRIMARY' : ($index-\u003eisUnique() ? 'UNIQUE' : 'INDEX');\n    $cols = implode(', ', array_map(fn($c) =\u003e $c-\u003egetValue(), $index-\u003egetColumns()));\n    echo \"  {$indexType} ({$cols})\";\n    if ($name = $index-\u003egetName()) {echo \" [{$name}]\";}\n    echo \"\\n\";\n}\n```\n\n### Column Properties\n\nThe `ColumnNode` represents a table column with its type, constraints, and metadata.\n\n**DBML Syntax:**\n\n```dbml\nTable products {\n    id int [pk, increment]\n    name varchar(255) [not null]\n    price decimal(10,2) [default: 0.00]\n    status product_status [not null]\n    created_at timestamp [default: `now()`]\n    note: 'Product catalog'\n}\n```\n\n#### Access Basic Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\Table\\ColumnNode $column */\n$column = $table-\u003egetColumn('price');\n\n// Column name\n$name = $column-\u003egetName();\n// Returns: string (e.g., 'price')\n\n// Source position\n$offset = $column-\u003egetOffset();\n// Returns: int\n\n// Column note\n$note = $column-\u003egetNote();\n// Returns: string|null\n```\n\n#### Get Column Type\n\n```php\n// Get type information\n$type = $column-\u003egetType();\n// Returns: TypeNode\n\n$typeName = $type-\u003egetName();\n// Returns: string (e.g., 'decimal', 'varchar', 'int')\n\n$size = $type-\u003egetSize();\n// Returns: int|null (first size parameter, e.g., 10 from decimal(10,2))\n\n$sizeArray = $type-\u003egetSizeArray();\n// Returns: int[] (all size parameters, e.g., [10, 2] from decimal(10,2))\n\n$offset = $type-\u003egetOffset();\n// Returns: int\n```\n\n**Type Examples:**\n\n```php\n// varchar(255)\n$type-\u003egetName();      // 'varchar'\n$type-\u003egetSize();      // 255\n$type-\u003egetSizeArray(); // [255]\n\n// decimal(10,2)\n$type-\u003egetName();      // 'decimal'\n$type-\u003egetSize();      // 10\n$type-\u003egetSizeArray(); // [10, 2]\n\n// int (no size)\n$type-\u003egetName();      // 'int'\n$type-\u003egetSize();      // null\n$type-\u003egetSizeArray(); // []\n```\n\n#### Check Column Constraints\n\n```php\n// Primary key\n$isPrimaryKey = $column-\u003eisPrimaryKey();\n// Returns: bool\n\n// Auto-increment\n$isIncrement = $column-\u003eisIncrement();\n// Returns: bool\n\n// Unique constraint\n$isUnique = $column-\u003eisUnique();\n// Returns: bool\n\n// Nullable\n$isNullable = $column-\u003eisNull();\n// Returns: bool (true = NULL, false = NOT NULL)\n```\n\n#### Get Default Value\n\n```php\n// Get default value\n$default = $column-\u003egetDefault();\n// Returns: AbstractValue|null\n\nif ($default !== null) {\n    $value = $default-\u003egetValue();\n    // Type depends on value node:\n    // - IntNode: int\n    // - FloatNode: float\n    // - StringNode: string|int|float|bool\n    // - BooleanNode: bool\n    // - NullNode: null\n    // - ExpressionNode: string (e.g., 'now()')\n}\n```\n\n**Default Value Types:**\n\n```php\nuse Butschster\\Dbml\\Ast\\Values\\{IntNode, FloatNode, StringNode, BooleanNode, NullNode, ExpressionNode};\n\n$default = $column-\u003egetDefault();\n\n// Check value type\nif ($default instanceof IntNode) {\n    $intValue = $default-\u003egetValue(); // int\n}\n\nif ($default instanceof FloatNode) {\n    $floatValue = $default-\u003egetValue(); // float\n}\n\nif ($default instanceof StringNode) {\n    $stringValue = $default-\u003egetValue(); // string|int|float|bool\n}\n\nif ($default instanceof BooleanNode) {\n    $boolValue = $default-\u003egetValue(); // bool\n}\n\nif ($default instanceof NullNode) {\n    $nullValue = $default-\u003egetValue(); // null\n}\n\nif ($default instanceof ExpressionNode) {\n    $expression = $default-\u003egetValue(); // string (e.g., 'now()')\n}\n```\n\n#### Get Additional Settings\n\n```php\n// Get custom settings (beyond standard constraints)\n$settings = $column-\u003egetSettings();\n// Returns: SettingWithValueNode[]\n\nforeach ($settings as $setting) {\n    $name = $setting-\u003egetName();   // string\n    $value = $setting-\u003egetValue(); // AbstractValue\n}\n```\n\n#### Get Column Relationships\n\n```php\n// Get foreign key references defined inline\n$refs = $column-\u003egetRefs();\n// Returns: RefNode[]\n\nforeach ($refs as $ref) {\n    $rightTable = $ref-\u003egetRightTable()-\u003egetTable();\n    $rightColumns = $ref-\u003egetRightTable()-\u003egetColumns();\n}\n```\n\n**Complete Column Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\Table\\ColumnNode;\n\n/** @var ColumnNode $column */\n\n// Basic info\necho \"Column: {$column-\u003egetName()}\\n\";\necho \"Type: {$column-\u003egetType()-\u003egetName()}\";\nif ($size = $column-\u003egetType()-\u003egetSize()) {\n    echo \"({$size})\";\n}\necho \"\\n\";\n\n// Constraints\n$constraints = [];\nif ($column-\u003eisPrimaryKey()) {$constraints[] = 'PRIMARY KEY';}\nif ($column-\u003eisIncrement()) {$constraints[] = 'AUTO_INCREMENT';}\nif ($column-\u003eisUnique()) {$constraints[] = 'UNIQUE';}\nif (!$column-\u003eisNull()) {$constraints[] = 'NOT NULL';}\n\nif (!empty($constraints)) {\n    echo \"Constraints: \" . implode(', ', $constraints) . \"\\n\";\n}\n\n// Default value\nif ($default = $column-\u003egetDefault()) {\n    $defaultValue = $default-\u003egetValue();\n    echo \"Default: \";\n    if ($default instanceof \\Butschster\\Dbml\\Ast\\Values\\ExpressionNode) {\n        echo \"`{$defaultValue}`\";\n    } else {\n        echo var_export($defaultValue, true);\n    }\n    echo \"\\n\";\n}\n\n// Note\nif ($note = $column-\u003egetNote()) {\n    echo \"Note: {$note}\\n\";\n}\n\n// Relationships\nif ($refs = $column-\u003egetRefs()) {\n    echo \"References:\\n\";\n    foreach ($refs as $ref) {\n        $table = $ref-\u003egetRightTable()-\u003egetTable();\n        $cols = implode(', ', $ref-\u003egetRightTable()-\u003egetColumns());\n        echo \"  -\u003e {$table}({$cols})\\n\";\n    }\n}\n```\n\n### Index Configuration\n\nThe `IndexNode` represents a table index, which can be single-column or composite.\n\n**DBML Syntax:**\n\n```dbml\nTable products {\n    id int\n    merchant_id int\n    status varchar\n    \n    Indexes {\n        id [pk]\n        (merchant_id, status) [name: 'merchant_status_idx', type: hash]\n        email [unique]\n    }\n}\n```\n\n#### Access Index Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\Table\\IndexNode $index */\n$index = $table-\u003egetIndexes()[0];\n\n// Index name (optional)\n$name = $index-\u003egetName();\n// Returns: string|null (e.g., 'merchant_status_idx')\n\n// Index type (optional)\n$type = $index-\u003egetType();\n// Returns: string|null (e.g., 'hash', 'btree')\n\n// Index note\n$note = $index-\u003egetNote();\n// Returns: string|null\n\n// Check if primary key\n$isPrimary = $index-\u003eisPrimaryKey();\n// Returns: bool\n\n// Check if unique\n$isUnique = $index-\u003eisUnique();\n// Returns: bool\n```\n\n#### Get Indexed Columns\n\n```php\n// Get columns in the index\n$columns = $index-\u003egetColumns();\n// Returns: AbstractValue[] (StringNode or ExpressionNode)\n\nforeach ($columns as $column) {\n    $columnName = $column-\u003egetValue();\n    // For StringNode: column name\n    // For ExpressionNode: expression like 'LOWER(email)'\n}\n\n// Example: Single column index\n// Indexes { email [unique] }\n$columns = $index-\u003egetColumns();\n// Returns: [StringNode('email')]\n\n// Example: Composite index\n// Indexes { (merchant_id, status) [name: 'idx'] }\n$columns = $index-\u003egetColumns();\n// Returns: [StringNode('merchant_id'), StringNode('status')]\n\n// Example: Expression index\n// Indexes { `LOWER(email)` [unique] }\n$columns = $index-\u003egetColumns();\n// Returns: [ExpressionNode('LOWER(email)')]\n```\n\n#### Get Custom Settings\n\n```php\n// Get all settings (including custom ones)\n$settings = $index-\u003egetSettings();\n// Returns: array (mixed setting types)\n\n// Settings can include:\n// - PrimaryKeyNode\n// - UniqueNode\n// - NoteNode\n// - SettingWithValueNode (for name, type, and custom settings)\n```\n\n**Complete Index Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\Table\\IndexNode;\nuse Butschster\\Dbml\\Ast\\Values\\{StringNode, ExpressionNode};\n\n/** @var IndexNode $index */\n\n// Index type\nif ($index-\u003eisPrimaryKey()) {\n    echo \"PRIMARY KEY\";\n} elseif ($index-\u003eisUnique()) {\n    echo \"UNIQUE INDEX\";\n} else {\n    echo \"INDEX\";\n}\n\n// Index name\nif ($name = $index-\u003egetName()) {\n    echo \" {$name}\";\n}\n\n// Columns\n$columnNames = array_map(\n    fn($col) =\u003e $col-\u003egetValue(),\n    $index-\u003egetColumns()\n);\necho \" (\" . implode(', ', $columnNames) . \")\";\n\n// Index type (hash, btree, etc.)\nif ($type = $index-\u003egetType()) {\n    echo \" USING {$type}\";\n}\n\n// Note\nif ($note = $index-\u003egetNote()) {\n    echo \"\\n  Note: {$note}\";\n}\n\necho \"\\n\";\n\n// Determine if composite\n$isComposite = count($index-\u003egetColumns()) \u003e 1;\necho $isComposite ? \"  (Composite index)\\n\" : \"  (Single column)\\n\";\n\n// Check for expression columns\n$hasExpressions = array_reduce(\n    $index-\u003egetColumns(),\n    fn($has, $col) =\u003e $has || $col instanceof ExpressionNode,\n    false\n);\nif ($hasExpressions) {\n    echo \"  (Contains expressions)\\n\";\n}\n```\n\n### Enum Management\n\nThe `EnumNode` represents an enumeration type that can be used as a column type.\n\n**DBML Syntax:**\n\n```dbml\nEnum order_status {\n    pending\n    processing\n    shipped\n    delivered [note: 'Order has been delivered to customer']\n}\n```\n\n#### Access Enum Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\EnumNode $enum */\n$enum = $schema-\u003egetEnum('order_status');\n\n// Enum name\n$name = $enum-\u003egetName();\n// Returns: string (e.g., 'order_status')\n\n// Number of values\n$count = $enum-\u003ecount();\n// Returns: int (e.g., 4)\n\n// Source position\n$offset = $enum-\u003egetOffset();\n// Returns: int\n```\n\n#### Get Enum Values\n\n```php\n// Get all values\n$values = $enum-\u003egetValues();\n// Returns: ValueNode[] (associative array keyed by value name)\n\nforeach ($values as $valueName =\u003e $valueNode) {\n    echo \"{$valueName}: {$valueNode-\u003egetValue()}\\n\";\n    if ($note = $valueNode-\u003egetNote()) {\n        echo \"  Note: {$note}\\n\";\n    }\n}\n\n// Check if value exists\nif ($enum-\u003ehasValue('shipped')) {\n    $value = $enum-\u003egetValue('shipped');\n    // Returns: ValueNode\n}\n\n// Throws EnumValueNotFoundException if not found\ntry {\n    $value = $enum-\u003egetValue('nonexistent');\n} catch (\\Butschster\\Dbml\\Exceptions\\EnumValueNotFoundException $e) {\n    // Handle missing enum value\n}\n```\n\n#### Access Value Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\Enum\\ValueNode $value */\n$value = $enum-\u003egetValue('delivered');\n\n// Value name\n$name = $value-\u003egetValue();\n// Returns: string (e.g., 'delivered')\n\n// Value note\n$note = $value-\u003egetNote();\n// Returns: string|null\n\n// Source position\n$offset = $value-\u003egetOffset();\n// Returns: int\n```\n\n**Complete Enum Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\EnumNode;\nuse Butschster\\Dbml\\Ast\\Enum\\ValueNode;\n\n/** @var EnumNode $enum */\n\necho \"Enum: {$enum-\u003egetName()}\\n\";\necho \"Values: {$enum-\u003ecount()}\\n\\n\";\n\n// List all values\nforeach ($enum-\u003egetValues() as $valueName =\u003e $value) {\n    echo \"  - {$valueName}\";\n    \n    if ($note = $value-\u003egetNote()) {\n        echo \" // {$note}\";\n    }\n    \n    echo \"\\n\";\n}\n\n// Check if specific values exist\n$requiredValues = ['pending', 'processing', 'completed'];\nforeach ($requiredValues as $required) {\n    if (!$enum-\u003ehasValue($required)) {\n        echo \"Warning: Missing required value '{$required}'\\n\";\n    }\n}\n\n// Generate PHP enum class\necho \"\\nenum {$enum-\u003egetName()}: string {\\n\";\nforeach ($enum-\u003egetValues() as $valueName =\u003e $value) {\n    echo \"    case \" . strtoupper($valueName) . \" = '{$valueName}';\\n\";\n}\necho \"}\\n\";\n```\n\n### Table Groups\n\nThe `TableGroupNode` represents a logical grouping of related tables.\n\n**DBML Syntax:**\n\n```dbml\nTableGroup core_tables {\n    users\n    roles\n    permissions\n}\n\nTableGroup e_commerce {\n    products\n    orders\n    order_items\n}\n```\n\n#### Access Group Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\TableGroupNode $group */\n$group = $schema-\u003egetTableGroup('core_tables');\n\n// Group name\n$name = $group-\u003egetName();\n// Returns: string (e.g., 'core_tables')\n\n// Source position\n$offset = $group-\u003egetOffset();\n// Returns: int\n```\n\n#### Get Group Tables\n\n```php\n// Get all table names in group\n$tables = $group-\u003egetTables();\n// Returns: string[] (array of table names)\n\nforeach ($tables as $tableName) {\n    if ($schema-\u003ehasTable($tableName)) {\n        $table = $schema-\u003egetTable($tableName);\n        // Process table\n    }\n}\n\n// Check if specific table is in group\nif ($group-\u003ehasTable('users')) {\n    echo \"users table is in {$group-\u003egetName()} group\\n\";\n}\n```\n\n**Complete Table Group Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\TableGroupNode;\nuse Butschster\\Dbml\\Ast\\SchemaNode;\n\n/** @var TableGroupNode $group */\n/** @var SchemaNode $schema */\n\necho \"Table Group: {$group-\u003egetName()}\\n\";\necho \"Tables (\" . count($group-\u003egetTables()) . \"):\\n\";\n\nforeach ($group-\u003egetTables() as $tableName) {\n    echo \"  - {$tableName}\";\n    \n    if ($schema-\u003ehasTable($tableName)) {\n        $table = $schema-\u003egetTable($tableName);\n        $columnCount = count($table-\u003egetColumns());\n        echo \" ({$columnCount} columns)\";\n    } else {\n        echo \" [TABLE NOT FOUND]\";\n    }\n    \n    echo \"\\n\";\n}\n\n// Group tables by their groups\n$allGroups = $schema-\u003egetTableGroups();\n$groupedTables = [];\n\nforeach ($allGroups as $grp) {\n    foreach ($grp-\u003egetTables() as $tableName) {\n        $groupedTables[$tableName][] = $grp-\u003egetName();\n    }\n}\n\n// Find tables in multiple groups\nforeach ($groupedTables as $tableName =\u003e $groups) {\n    if (count($groups) \u003e 1) {\n        echo \"Table '{$tableName}' is in multiple groups: \" . implode(', ', $groups) . \"\\n\";\n    }\n}\n```\n\n### Relationships (Refs)\n\nThe `RefNode` represents a foreign key relationship between tables.\n\n**DBML Syntax:**\n\n```dbml\n// Inline relationship\nTable orders {\n    user_id int [ref: \u003e users.id]\n}\n\n// Standalone relationship (short form)\nRef: orders.user_id \u003e users.id\n\n// Standalone relationship (long form with actions)\nRef order_user_fk: products.merchant_id \u003e merchants.id [\n    delete: cascade,\n    update: no action\n]\n\n// Composite foreign key\nRef: order_items.(order_id, product_id) \u003e orders.(id, product_id)\n\n// Relationship types:\n// \u003e  : many-to-one\n// \u003c  : one-to-many\n// -  : one-to-one\n\n// Grouped relationships\nRef {\n    products.category_id \u003e categories.id\n    products.merchant_id \u003e merchants.id\n}\n```\n\n#### Access Relationship Properties\n\n```php\n/** @var \\Butschster\\Dbml\\Ast\\RefNode $ref */\n$ref = $schema-\u003egetRefs()[0];\n\n// Relationship name (optional)\n$name = $ref-\u003egetName();\n// Returns: string|null (e.g., 'order_user_fk')\n\n// Source position\n$offset = $ref-\u003egetOffset();\n// Returns: int\n```\n\n#### Get Relationship Type\n\n```php\n// Get relationship type\n$type = $ref-\u003egetType();\n// Returns: TypeNode (one of the following)\n\nuse Butschster\\Dbml\\Ast\\Ref\\Type\\{ManyToOneNode, OneToManyNode, OneToOneNode};\n\nif ($type instanceof ManyToOneNode) {\n    echo \"Many-to-One (\u003e)\\n\";\n} elseif ($type instanceof OneToManyNode) {\n    echo \"One-to-Many (\u003c)\\n\";\n} elseif ($type instanceof OneToOneNode) {\n    echo \"One-to-One (-)\\n\";\n}\n```\n\n#### Get Referenced Tables\n\n```php\n// Left side of relationship (source)\n$leftTable = $ref-\u003egetLeftTable();\n// Returns: LeftTableNode\n\n$leftTableName = $leftTable-\u003egetTable();\n// Returns: string (e.g., 'orders')\n\n$leftColumns = $leftTable-\u003egetColumns();\n// Returns: string[] (e.g., ['user_id'])\n\n// Right side of relationship (target)\n$rightTable = $ref-\u003egetRightTable();\n// Returns: RightTableNode\n\n$rightTableName = $rightTable-\u003egetTable();\n// Returns: string (e.g., 'users')\n\n$rightColumns = $rightTable-\u003egetColumns();\n// Returns: string[] (e.g., ['id'])\n```\n\n**Composite Key Example:**\n\n```dbml\nRef: order_items.(order_id, product_id) \u003e orders.(id, product_id)\n```\n\n```php\n$leftColumns = $ref-\u003egetLeftTable()-\u003egetColumns();\n// Returns: ['order_id', 'product_id']\n\n$rightColumns = $ref-\u003egetRightTable()-\u003egetColumns();\n// Returns: ['id', 'product_id']\n```\n\n#### Get Referential Actions\n\n```php\n// Get all actions\n$actions = $ref-\u003egetActions();\n// Returns: ActionNode[] (associative array keyed by action name)\n\nforeach ($actions as $actionName =\u003e $action) {\n    echo \"{$actionName}: {$action-\u003egetAction()}\\n\";\n}\n\n// Check if action exists\nif ($ref-\u003ehasAction('delete')) {\n    $action = $ref-\u003egetAction('delete');\n    // Returns: OnDeleteNode\n    \n    $actionType = $action-\u003egetAction();\n    // Returns: string (cascade, restrict, set null, set default, no action)\n}\n\n// Throws RefActionNotFoundException if not found\ntry {\n    $action = $ref-\u003egetAction('nonexistent');\n} catch (\\Butschster\\Dbml\\Exceptions\\RefActionNotFoundException $e) {\n    // Handle missing action\n}\n```\n\n**Action Types:**\n\n| Action        | Description                               |\n|---------------|-------------------------------------------|\n| `cascade`     | Automatically update/delete related rows  |\n| `restrict`    | Prevent action if related rows exist      |\n| `set null`    | Set foreign key to NULL                   |\n| `set default` | Set foreign key to default value          |\n| `no action`   | No automatic action (similar to restrict) |\n\n**Action Node Types:**\n\n```php\nuse Butschster\\Dbml\\Ast\\Ref\\Action\\{OnDeleteNode, OnUpdateNode};\n\n// Check action type\nif ($action instanceof OnDeleteNode) {\n    echo \"ON DELETE {$action-\u003egetAction()}\\n\";\n}\n\nif ($action instanceof OnUpdateNode) {\n    echo \"ON UPDATE {$action-\u003egetAction()}\\n\";\n}\n\n// Both extend ActionNode\n$actionName = $action-\u003egetName(); // 'delete' or 'update'\n$actionType = $action-\u003egetAction(); // 'cascade', 'restrict', etc.\n```\n\n**Complete Relationship Example:**\n\n```php\nuse Butschster\\Dbml\\Ast\\RefNode;\nuse Butschster\\Dbml\\Ast\\Ref\\Type\\{ManyToOneNode, OneToManyNode, OneToOneNode};\n\n/** @var RefNode $ref */\n\n// Relationship name\nif ($name = $ref-\u003egetName()) {\n    echo \"Relationship: {$name}\\n\";\n}\n\n// Tables and columns\n$left = $ref-\u003egetLeftTable();\n$right = $ref-\u003egetRightTable();\n\necho \"{$left-\u003egetTable()}.\" . implode(', ', $left-\u003egetColumns());\n\n// Relationship type\n$type = $ref-\u003egetType();\nif ($type instanceof ManyToOneNode) {\n    echo \" \u003e \";\n} elseif ($type instanceof OneToManyNode) {\n    echo \" \u003c \";\n} else {\n    echo \" - \";\n}\n\necho \"{$right-\u003egetTable()}.\" . implode(', ', $right-\u003egetColumns());\necho \"\\n\";\n\n// Actions\nif ($actions = $ref-\u003egetActions()) {\n    echo \"Actions:\\n\";\n    foreach ($actions as $actionName =\u003e $action) {\n        echo \"  ON \" . strtoupper($actionName) . \" {$action-\u003egetAction()}\\n\";\n    }\n}\n\n// Generate SQL\n$leftCols = implode(', ', $left-\u003egetColumns());\n$rightCols = implode(', ', $right-\u003egetColumns());\n\necho \"\\nSQL:\\n\";\necho \"ALTER TABLE {$left-\u003egetTable()}\\n\";\necho \"  ADD CONSTRAINT \" . ($name ?: \"fk_{$left-\u003egetTable()}_{$right-\u003egetTable()}\") . \"\\n\";\necho \"  FOREIGN KEY ({$leftCols})\\n\";\necho \"  REFERENCES {$right-\u003egetTable()}({$rightCols})\";\n\nif ($ref-\u003ehasAction('delete')) {\n    echo \"\\n  ON DELETE \" . strtoupper($ref-\u003egetAction('delete')-\u003egetAction());\n}\nif ($ref-\u003ehasAction('update')) {\n    echo \"\\n  ON UPDATE \" . strtoupper($ref-\u003egetAction('update')-\u003egetAction());\n}\necho \";\\n\";\n```\n\n## Advanced Usage\n\n### Validating Schema Integrity\n\n```php\nuse Butschster\\Dbml\\Ast\\SchemaNode;\n\nfunction validateSchema(SchemaNode $schema): array\n{\n    $errors = [];\n    \n    // Check for orphaned foreign keys\n    foreach ($schema-\u003egetRefs() as $ref) {\n        $leftTable = $ref-\u003egetLeftTable()-\u003egetTable();\n        $rightTable = $ref-\u003egetRightTable()-\u003egetTable();\n        \n        if (!$schema-\u003ehasTable($leftTable)) {\n            $errors[] = \"Reference from non-existent table: {$leftTable}\";\n        }\n        \n        if (!$schema-\u003ehasTable($rightTable)) {\n            $errors[] = \"Reference to non-existent table: {$rightTable}\";\n        }\n        \n        // Validate columns exist\n        if ($schema-\u003ehasTable($leftTable)) {\n            $table = $schema-\u003egetTable($leftTable);\n            foreach ($ref-\u003egetLeftTable()-\u003egetColumns() as $col) {\n                if (!$table-\u003ehasColumn($col)) {\n                    $errors[] = \"Column {$leftTable}.{$col} not found\";\n                }\n            }\n        }\n    }\n    \n    // Check for duplicate column names\n    foreach ($schema-\u003egetTables() as $table) {\n        $columnNames = array_map(fn($c) =\u003e $c-\u003egetName(), $table-\u003egetColumns());\n        $duplicates = array_filter(\n            array_count_values($columnNames),\n            fn($count) =\u003e $count \u003e 1\n        );\n        \n        foreach ($duplicates as $colName =\u003e $count) {\n            $errors[] = \"Duplicate column '{$colName}' in table {$table-\u003egetName()}\";\n        }\n    }\n    \n    return $errors;\n}\n\n$errors = validateSchema($schema);\nif (!empty($errors)) {\n    foreach ($errors as $error) {\n        echo \"Error: {$error}\\n\";\n    }\n}\n```\n\n### Generating Documentation\n\n```php\nuse Butschster\\Dbml\\Ast\\SchemaNode;\n\nfunction generateMarkdownDocs(SchemaNode $schema): string\n{\n    $md = \"# Database Schema\\n\\n\";\n    \n    if ($project = $schema-\u003egetProject()) {\n        $md .= \"**Project:** {$project-\u003egetName()}\\n\\n\";\n        \n        if ($note = $project-\u003egetNote()) {\n            $md .= \"\u003e {$note}\\n\\n\";\n        }\n    }\n    \n    // Tables\n    $md .= \"## Tables\\n\\n\";\n    foreach ($schema-\u003egetTables() as $table) {\n        $md .= \"### {$table-\u003egetName()}\\n\\n\";\n        \n        if ($note = $table-\u003egetNote()) {\n            $md .= \"*{$note}*\\n\\n\";\n        }\n        \n        // Columns table\n        $md .= \"| Column | Type | Constraints |\\n\";\n        $md .= \"|--------|------|-------------|\\n\";\n        \n        foreach ($table-\u003egetColumns() as $column) {\n            $type = $column-\u003egetType()-\u003egetName();\n            if ($size = $column-\u003egetType()-\u003egetSize()) {\n                $type .= \"({$size})\";\n            }\n            \n            $constraints = [];\n            if ($column-\u003eisPrimaryKey()) {$constraints[] = 'PK';}\n            if ($column-\u003eisUnique()) {$constraints[] = 'UNIQUE';}\n            if (!$column-\u003eisNull()) {$constraints[] = 'NOT NULL';}\n            if ($column-\u003eisIncrement()) {$constraints[] = 'AUTO_INCREMENT';}\n            \n            $md .= \"| {$column-\u003egetName()} | {$type} | \" . implode(', ', $constraints) . \" |\\n\";\n        }\n        \n        $md .= \"\\n\";\n    }\n    \n    // Enums\n    if (!empty($schema-\u003egetEnums())) {\n        $md .= \"## Enums\\n\\n\";\n        foreach ($schema-\u003egetEnums() as $enum) {\n            $md .= \"### {$enum-\u003egetName()}\\n\\n\";\n            foreach ($enum-\u003egetValues() as $value) {\n                $md .= \"- `{$value-\u003egetValue()}`\";\n                if ($note = $value-\u003egetNote()) {\n                    $md .= \" - {$note}\";\n                }\n                $md .= \"\\n\";\n            }\n            $md .= \"\\n\";\n        }\n    }\n    \n    return $md;\n}\n\n$markdown = generateMarkdownDocs($schema);\nfile_put_contents('schema.md', $markdown);\n```\n\n### Converting to Different Formats\n\n```php\nuse Butschster\\Dbml\\Ast\\SchemaNode;\n\nfunction convertToArray(SchemaNode $schema): array\n{\n    $result = [];\n    \n    // Project\n    if ($project = $schema-\u003egetProject()) {\n        $result['project'] = [\n            'name' =\u003e $project-\u003egetName(),\n            'note' =\u003e $project-\u003egetNote(),\n            'settings' =\u003e array_map(\n                fn($s) =\u003e ['key' =\u003e $s-\u003egetKey(), 'value' =\u003e $s-\u003egetValue()],\n                $project-\u003egetSettings()\n            ),\n        ];\n    }\n    \n    // Tables\n    $result['tables'] = [];\n    foreach ($schema-\u003egetTables() as $table) {\n        $result['tables'][$table-\u003egetName()] = [\n            'alias' =\u003e $table-\u003egetAlias(),\n            'note' =\u003e $table-\u003egetNote(),\n            'columns' =\u003e array_map(function($col) {\n                return [\n                    'name' =\u003e $col-\u003egetName(),\n                    'type' =\u003e $col-\u003egetType()-\u003egetName(),\n                    'size' =\u003e $col-\u003egetType()-\u003egetSizeArray(),\n                    'primary_key' =\u003e $col-\u003eisPrimaryKey(),\n                    'unique' =\u003e $col-\u003eisUnique(),\n                    'nullable' =\u003e $col-\u003eisNull(),\n                    'increment' =\u003e $col-\u003eisIncrement(),\n                    'default' =\u003e $col-\u003egetDefault()?-\u003egetValue(),\n                    'note' =\u003e $col-\u003egetNote(),\n                ];\n            }, $table-\u003egetColumns()),\n            'indexes' =\u003e array_map(function($idx) {\n                return [\n                    'name' =\u003e $idx-\u003egetName(),\n                    'columns' =\u003e array_map(fn($c) =\u003e $c-\u003egetValue(), $idx-\u003egetColumns()),\n                    'primary_key' =\u003e $idx-\u003eisPrimaryKey(),\n                    'unique' =\u003e $idx-\u003eisUnique(),\n                    'type' =\u003e $idx-\u003egetType(),\n                ];\n            }, $table-\u003egetIndexes()),\n        ];\n    }\n    \n    // Enums\n    $result['enums'] = [];\n    foreach ($schema-\u003egetEnums() as $enum) {\n        $result['enums'][$enum-\u003egetName()] = array_map(\n            fn($v) =\u003e ['value' =\u003e $v-\u003egetValue(), 'note' =\u003e $v-\u003egetNote()],\n            $enum-\u003egetValues()\n        );\n    }\n    \n    // Relationships\n    $result['relationships'] = array_map(function($ref) {\n        $type = match (get_class($ref-\u003egetType())) {\n            \\Butschster\\Dbml\\Ast\\Ref\\Type\\ManyToOneNode::class =\u003e 'many-to-one',\n            \\Butschster\\Dbml\\Ast\\Ref\\Type\\OneToManyNode::class =\u003e 'one-to-many',\n            \\Butschster\\Dbml\\Ast\\Ref\\Type\\OneToOneNode::class =\u003e 'one-to-one',\n        };\n        \n        return [\n            'name' =\u003e $ref-\u003egetName(),\n            'type' =\u003e $type,\n            'from' =\u003e [\n                'table' =\u003e $ref-\u003egetLeftTable()-\u003egetTable(),\n                'columns' =\u003e $ref-\u003egetLeftTable()-\u003egetColumns(),\n            ],\n            'to' =\u003e [\n                'table' =\u003e $ref-\u003egetRightTable()-\u003egetTable(),\n                'columns' =\u003e $ref-\u003egetRightTable()-\u003egetColumns(),\n            ],\n            'actions' =\u003e array_map(\n                fn($a) =\u003e ['name' =\u003e $a-\u003egetName(), 'action' =\u003e $a-\u003egetAction()],\n                $ref-\u003egetActions()\n            ),\n        ];\n    }, $schema-\u003egetRefs());\n    \n    return $result;\n}\n\n// Convert to JSON\n$json = json_encode(convertToArray($schema), JSON_PRETTY_PRINT);\nfile_put_contents('schema.json', $json);\n\n// Convert to YAML\n$yaml = yaml_emit(convertToArray($schema));\nfile_put_contents('schema.yaml', $yaml);\n```\n\n### Generating Migration Code\n\n```php\nuse Butschster\\Dbml\\Ast\\{SchemaNode, TableNode};\n\nfunction generateLaravelMigration(TableNode $table): string\n{\n    $className = 'Create' . str_replace('_', '', ucwords($table-\u003egetName(), '_')) . 'Table';\n    $tableName = $table-\u003egetName();\n    \n    $code = \"\u003c?php\\n\\n\";\n    $code .= \"use Illuminate\\Database\\Migrations\\Migration;\\n\";\n    $code .= \"use Illuminate\\Database\\Schema\\Blueprint;\\n\";\n    $code .= \"use Illuminate\\Support\\Facades\\Schema;\\n\\n\";\n    $code .= \"return new class extends Migration\\n{\\n\";\n    $code .= \"    public function up(): void\\n    {\\n\";\n    $code .= \"        Schema::create('{$tableName}', function (Blueprint \\$table) {\\n\";\n    \n    foreach ($table-\u003egetColumns() as $column) {\n        $type = $column-\u003egetType()-\u003egetName();\n        $name = $column-\u003egetName();\n        \n        // Convert DBML types to Laravel types\n        $laravelType = match($type) {\n            'varchar' =\u003e 'string',\n            'int' =\u003e 'integer',\n            'bool', 'boolean' =\u003e 'boolean',\n            'text' =\u003e 'text',\n            'datetime' =\u003e 'dateTime',\n            'timestamp' =\u003e 'timestamp',\n            default =\u003e $type,\n        };\n        \n        $line = \"            \\$table-\u003e{$laravelType}('{$name}'\";\n        \n        if ($size = $column-\u003egetType()-\u003egetSize()) {\n            $line .= \", {$size}\";\n        }\n        \n        $line .= \")\";\n        \n        if ($column-\u003eisUnique()) {$line .= \"-\u003eunique()\";}\n        if (!$column-\u003eisNull()) {$line .= \"-\u003enullable(false)\";}\n        if ($column-\u003eisIncrement()) {$line .= \"-\u003eautoIncrement()\";}\n        if ($default = $column-\u003egetDefault()) {\n            $line .= \"-\u003edefault(\" . var_export($default-\u003egetValue(), true) . \")\";\n        }\n        \n        $line .= \";\\n\";\n        $code .= $line;\n    }\n    \n    $code .= \"        });\\n\";\n    $code .= \"    }\\n\\n\";\n    $code .= \"    public function down(): void\\n    {\\n\";\n    $code .= \"        Schema::dropIfExists('{$tableName}');\\n\";\n    $code .= \"    }\\n\";\n    $code .= \"};\\n\";\n    \n    return $code;\n}\n\n// Generate migrations for all tables\nforeach ($schema-\u003egetTables() as $table) {\n    $migration = generateLaravelMigration($table);\n    $filename = date('Y_m_d_His') . \"_create_{$table-\u003egetName()}_table.php\";\n    file_put_contents(\"database/migrations/{$filename}\", $migration);\n}\n```\n\n## Use Cases\n\n### 1. Database Schema Version Control\n\nStore your database design in DBML format alongside your application code:\n\n```php\n// Store schema in version control\n$dbml = file_get_contents('schema/database.dbml');\n$parser = DbmlParserFactory::create();\n$schema = $parser-\u003eparse($dbml);\n\n// Validate before deployment\n$errors = validateSchema($schema);\nif (!empty($errors)) {\n    throw new Exception(\"Schema validation failed: \" . implode(', ', $errors));\n}\n```\n\n### 2. ORM Entity Generation\n\nGenerate Doctrine, Eloquent, or Cycle ORM entities:\n\n```php\nuse Butschster\\Dbml\\Ast\\TableNode;\n\nfunction generateCycleEntity(TableNode $table): string\n{\n    $className = str_replace('_', '', ucwords($table-\u003egetName(), '_'));\n    \n    $code = \"\u003c?php\\n\\nnamespace App\\Entities;\\n\\n\";\n    $code .= \"use Cycle\\Annotated\\Annotation as Cycle;\\n\\n\";\n    $code .= \"#[Cycle\\Entity(table: '{$table-\u003egetName()}')]\\n\";\n    $code .= \"class {$className}\\n{\\n\";\n    \n    foreach ($table-\u003egetColumns() as $column) {\n        $code .= \"    #[Cycle\\Column(type: '{$column-\u003egetType()-\u003egetName()}'\";\n        if ($column-\u003eisPrimaryKey()) $code .= \", primary: true\";\n        if (!$column-\u003eisNull()) $code .= \", nullable: false\";\n        $code .= \")]\\n\";\n        $code .= \"    private {$column-\u003egetName()};\\n\\n\";\n    }\n    \n    $code .= \"}\\n\";\n    return $code;\n}\n```\n\n### 3. API Documentation Generation\n\nCreate OpenAPI/Swagger specifications from your database schema:\n\n```php\nfunction generateOpenAPISchema(SchemaNode $schema): array\n{\n    $openapi = [\n        'openapi' =\u003e '3.0.0',\n        'info' =\u003e [\n            'title' =\u003e $schema-\u003egetProject()?-\u003egetName() ?? 'API',\n            'version' =\u003e '1.0.0',\n        ],\n        'components' =\u003e [\n            'schemas' =\u003e [],\n        ],\n    ];\n    \n    foreach ($schema-\u003egetTables() as $table) {\n        $properties = [];\n        $required = [];\n        \n        foreach ($table-\u003egetColumns() as $column) {\n            $type = match($column-\u003egetType()-\u003egetName()) {\n                'int', 'integer' =\u003e 'integer',\n                'varchar', 'text' =\u003e 'string',\n                'bool', 'boolean' =\u003e 'boolean',\n                'decimal', 'float' =\u003e 'number',\n                default =\u003e 'string',\n            };\n            \n            $properties[$column-\u003egetName()] = ['type' =\u003e $type];\n            \n            if (!$column-\u003eisNull()) {\n                $required[] = $column-\u003egetName();\n            }\n        }\n        \n        $openapi['components']['schemas'][$table-\u003egetName()] = [\n            'type' =\u003e 'object',\n            'properties' =\u003e $properties,\n            'required' =\u003e $required,\n        ];\n    }\n    \n    return $openapi;\n}\n```\n\n### 4. Database Comparison Tool\n\nCompare two DBML schemas to detect changes:\n\n```php\nfunction compareSchemas(SchemaNode $old, SchemaNode $new): array\n{\n    $changes = [];\n    \n    // New tables\n    foreach ($new-\u003egetTables() as $table) {\n        if (!$old-\u003ehasTable($table-\u003egetName())) {\n            $changes[] = \"Added table: {$table-\u003egetName()}\";\n        }\n    }\n    \n    // Removed tables\n    foreach ($old-\u003egetTables() as $table) {\n        if (!$new-\u003ehasTable($table-\u003egetName())) {\n            $changes[] = \"Removed table: {$table-\u003egetName()}\";\n        }\n    }\n    \n    // Modified tables\n    foreach ($new-\u003egetTables() as $newTable) {\n        if ($old-\u003ehasTable($newTable-\u003egetName())) {\n            $oldTable = $old-\u003egetTable($newTable-\u003egetName());\n            \n            // Column changes\n            foreach ($newTable-\u003egetColumns() as $column) {\n                if (!$oldTable-\u003ehasColumn($column-\u003egetName())) {\n                    $changes[] = \"Added column: {$newTable-\u003egetName()}.{$column-\u003egetName()}\";\n                }\n            }\n        }\n    }\n    \n    return $changes;\n}\n```\n\n### 5. Schema Visualization\n\nGenerate GraphViz DOT format for visual diagrams:\n\n```php\nfunction generateDotGraph(SchemaNode $schema): string\n{\n    $dot = \"digraph schema {\\n\";\n    $dot .= \"  node [shape=record];\\n\\n\";\n    \n    // Tables\n    foreach ($schema-\u003egetTables() as $table) {\n        $label = \"{\" . $table-\u003egetName() . \"|\";\n        $columns = [];\n        foreach ($table-\u003egetColumns() as $column) {\n            $col = $column-\u003egetName();\n            if ($column-\u003eisPrimaryKey()) {$col .= \" (PK)\";}\n            $columns[] = $col;\n        }\n        $label .= implode(\"\\\\n\", $columns) . \"}\";\n        \n        $dot .= \"  {$table-\u003egetName()} [label=\\\"{$label}\\\"];\\n\";\n    }\n    \n    $dot .= \"\\n\";\n    \n    // Relationships\n    foreach ($schema-\u003egetRefs() as $ref) {\n        $from = $ref-\u003egetLeftTable()-\u003egetTable();\n        $to = $ref-\u003egetRightTable()-\u003egetTable();\n        $dot .= \"  {$from} -\u003e {$to};\\n\";\n    }\n    \n    $dot .= \"}\\n\";\n    return $dot;\n}\n\n// Generate PNG: dot -Tpng schema.dot -o schema.png\nfile_put_contents('schema.dot', generateDotGraph($schema));\n```\n\n## Error Handling\n\nThe parser throws specific exceptions for different error conditions:\n\n```php\nuse Butschster\\Dbml\\Exceptions\\{\n    GrammarFileNotFoundException,\n    TableNotFoundException,\n    ColumnNotFoundException,\n    EnumNotFoundException,\n    EnumValueNotFoundException,\n    TableGroupNotFoundException,\n    ProjectSettingNotFoundException,\n    RefActionNotFoundException\n};\n\ntry {\n    // Parse DBML\n    $parser = DbmlParserFactory::create();\n    $schema = $parser-\u003eparse($dbml);\n    \n    // Access components\n    $table = $schema-\u003egetTable('users');\n    $column = $table-\u003egetColumn('email');\n    $enum = $schema-\u003egetEnum('status');\n    $value = $enum-\u003egetValue('active');\n    \n} catch (GrammarFileNotFoundException $e) {\n    // Grammar file missing (shouldn't happen in normal usage)\n    error_log(\"Parser initialization failed: \" . $e-\u003egetMessage());\n    \n} catch (TableNotFoundException $e) {\n    // Table doesn't exist in schema\n    error_log(\"Table not found: \" . $e-\u003egetMessage());\n    \n} catch (ColumnNotFoundException $e) {\n    // Column doesn't exist in table\n    error_log(\"Column not found: \" . $e-\u003egetMessage());\n    \n} catch (EnumNotFoundException $e) {\n    // Enum type doesn't exist\n    error_log(\"Enum not found: \" . $e-\u003egetMessage());\n    \n} catch (EnumValueNotFoundException $e) {\n    // Enum value doesn't exist\n    error_log(\"Enum value not found: \" . $e-\u003egetMessage());\n    \n} catch (TableGroupNotFoundException $e) {\n    // Table group doesn't exist\n    error_log(\"Table group not found: \" . $e-\u003egetMessage());\n    \n} catch (ProjectSettingNotFoundException $e) {\n    // Project setting doesn't exist\n    error_log(\"Project setting not found: \" . $e-\u003egetMessage());\n    \n} catch (RefActionNotFoundException $e) {\n    // Referential action doesn't exist\n    error_log(\"Ref action not found: \" . $e-\u003egetMessage());\n    \n} catch (\\Phplrt\\Contracts\\Exception\\RuntimeExceptionInterface $e) {\n    // Parser error (syntax error in DBML)\n    error_log(\"DBML parsing failed: \" . $e-\u003egetMessage());\n}\n```\n\n**Best Practices:**\n\n1. **Always check existence before accessing** using `has*()` methods\n2. **Catch specific exceptions** rather than generic Exception\n3. **Validate DBML syntax** before parsing in production\n4. **Handle parser errors gracefully** with user-friendly messages\n\n## Contributing\n\nContributions are welcome! Please follow these guidelines:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Write tests for your changes\n4. Ensure all tests pass (`composer test`)\n5. Commit your changes (`git commit -m 'Add amazing feature'`)\n6. Push to the branch (`git push origin feature/amazing-feature`)\n7. Open a Pull Request\n\n## Credits\n\n- **Author**: [Pavel Buchnev (butschster)](https://github.com/butschster)\n- **Parser Library**: Built with [phplrt](https://phplrt.org)\n- **Inspiration**: [dbdiagram.io](https://dbdiagram.io/)\n- **Specification**: [DBML Official Docs](https://www.dbml.org/)\n\n## License\n\nThis package is open-sourced software licensed under the [MIT license](LICENSE).\n\n---\n\n**Support this project:**\n\n- ⭐ Star this repository\n- 🐛 Report bugs and suggest features via [GitHub Issues](https://github.com/butschster/dbml-parser/issues)\n- 💖 [Support on Patreon](https://patreon.com/butschster)\n- 📢 Share with your network\n\nFor more information about DBML syntax and features, visit [www.dbml.org](https://www.dbml.org/).\n","funding_links":["https://patreon.com/butschster"],"categories":["PHP"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbutschster%2Fdbml-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbutschster%2Fdbml-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbutschster%2Fdbml-parser/lists"}