|
| 1 | +# Phobos Framework v3.0.2 Database Layer |
| 2 | +## Project Overview |
| 3 | + |
| 4 | +This is the **PhobosFramework Database Layer** - a standalone PHP 8.3+ database abstraction library that provides: |
| 5 | +- Query Builder with fluent interface for SELECT, INSERT, UPDATE, DELETE operations |
| 6 | +- Connection management with multiple database support via drivers |
| 7 | +- Entity-based ORM with Active Record pattern |
| 8 | +- Transaction management with nested transaction support (savepoints) |
| 9 | +- Schema aliasing for multi-tenant or multi-environment scenarios |
| 10 | + |
| 11 | +This is a **library package** (not an application) meant to be used via Composer by the main PhobosFramework or standalone projects. |
| 12 | + |
| 13 | +## Architecture Overview |
| 14 | + |
| 15 | +### Core Components |
| 16 | + |
| 17 | +**Connection Layer** (`src/Connection/`) |
| 18 | +- `ConnectionManager`: Singleton that manages multiple database connections, drivers, and configurations |
| 19 | +- `PDOConnection`: PDO-based connection implementation |
| 20 | +- `TransactionManager`: Handles transactions with nested support via savepoints |
| 21 | +- All connections are lazy-loaded (created only when first accessed) |
| 22 | + |
| 23 | +**Query Builder** (`src/QueryBuilder/`) |
| 24 | +- `QueryBuilder`: Main fluent interface for SELECT queries with support for joins, subqueries, unions |
| 25 | +- `InsertQuery`, `UpdateQuery`, `DeleteQuery`: Specialized builders for write operations |
| 26 | +- `Clauses/`: Individual clause implementations (WHERE, JOIN, ORDER BY, etc.) that compose into complete queries |
| 27 | +- All queries use prepared statements with parameter binding for security |
| 28 | +- The `getQueryWithBindings()` method returns both SQL and bindings (useful for dry-run mode) |
| 29 | + |
| 30 | +**Entity System** (`src/Entity/`) |
| 31 | +- `EntityManager`: Abstract base class with hydration, change tracking, and connection handling |
| 32 | +- `TableEntity`: Active Record implementation for database tables with CRUD operations |
| 33 | +- `ViewEntity`: Read-only entities for database views |
| 34 | +- `StoredProcedureEntity`: Entity for stored procedure calls |
| 35 | +- Entities track state: new vs. persisted, dirty fields, original values |
| 36 | + |
| 37 | +**Schema Registry** (`src/Schema/`) |
| 38 | +- `SchemaRegistry`: Singleton that maps schema aliases to real schema names |
| 39 | +- Allows switching schemas (e.g., dev/staging/prod) without changing entity code |
| 40 | +- Example: `schemaAlias('app', 'myapp_production')` maps all entities using 'app' to 'myapp_production' |
| 41 | + |
| 42 | +**Service Provider** (`src/DatabaseServiceProvider.php`) |
| 43 | +- Integrates with PhobosFramework's dependency injection container |
| 44 | +- Registers database services: `ConnectionManager`, `ConnectionInterface`, `TransactionManager` |
| 45 | +- Loads configuration from `config('database')` and registers drivers/connections |
| 46 | + |
| 47 | +### Key Design Patterns |
| 48 | + |
| 49 | +1. **Singleton Pattern**: `ConnectionManager` and `SchemaRegistry` are singletons |
| 50 | +2. **Active Record**: `TableEntity` combines data and database operations |
| 51 | +3. **Fluent Interface**: Query builders chain method calls |
| 52 | +4. **Strategy Pattern**: Different drivers via `DriverInterface` |
| 53 | +5. **Change Tracking**: Entities track dirty fields to optimize UPDATEs |
| 54 | + |
| 55 | +### Database Configuration Structure |
| 56 | + |
| 57 | +Expected configuration format (typically in `config/database.php`): |
| 58 | +```php |
| 59 | +[ |
| 60 | + 'default' => 'mysql', |
| 61 | + 'drivers' => [ |
| 62 | + 'mysql' => MySQLDriver::class, |
| 63 | + 'pgsql' => PostgreSQLDriver::class, |
| 64 | + ], |
| 65 | + 'connections' => [ |
| 66 | + 'mysql' => [ |
| 67 | + 'driver' => 'mysql', |
| 68 | + 'host' => 'localhost', |
| 69 | + 'database' => 'mydb', |
| 70 | + 'username' => 'user', |
| 71 | + 'password' => 'pass', |
| 72 | + ], |
| 73 | + ], |
| 74 | +] |
| 75 | +``` |
| 76 | + |
| 77 | +## Common Development Commands |
| 78 | + |
| 79 | +### Composer Operations |
| 80 | +```bash |
| 81 | +# Install dependencies |
| 82 | +composer install |
| 83 | + |
| 84 | +# Update dependencies |
| 85 | +composer update |
| 86 | + |
| 87 | +# Validate composer.json |
| 88 | +composer validate |
| 89 | + |
| 90 | +# Dump autoloader (after adding new classes) |
| 91 | +composer dump-autoload |
| 92 | +``` |
| 93 | + |
| 94 | +### Testing |
| 95 | +No test framework is currently configured. To add tests: |
| 96 | +1. Add PHPUnit to dev dependencies: `composer require --dev phpunit/phpunit` |
| 97 | +2. Create `phpunit.xml` configuration |
| 98 | +3. Create tests in `tests/` directory |
| 99 | + |
| 100 | +### Code Quality |
| 101 | +```bash |
| 102 | +# PHP syntax check |
| 103 | +find src -name "*.php" -exec php -l {} \; |
| 104 | + |
| 105 | +# Check PSR-4 autoloading |
| 106 | +composer dump-autoload --optimize |
| 107 | +``` |
| 108 | + |
| 109 | +## Working with Entities |
| 110 | + |
| 111 | +### Creating a New Table Entity |
| 112 | + |
| 113 | +1. **Create the entity class** in `src/Entity/` or your application: |
| 114 | +```php |
| 115 | +namespace App\Entities; |
| 116 | + |
| 117 | +use PhobosFramework\Database\Entity\TableEntity; |
| 118 | + |
| 119 | +class User extends TableEntity { |
| 120 | + protected static string $schema = 'app'; // Alias that resolves via SchemaRegistry |
| 121 | + protected static string $entity = 'users'; |
| 122 | + protected static array $pk = ['id']; |
| 123 | + protected static ?string $connection = null; // null = use default |
| 124 | + |
| 125 | + public int $id; |
| 126 | + public string $username; |
| 127 | + public string $email; |
| 128 | + public ?string $created_at; |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +2. **Reserved properties**: Do NOT use these property names: |
| 133 | + - `_isNew`, `_original`, `_dirty`, `_reserved` (internal state tracking) |
| 134 | + - `schema`, `entity`, `pk` (static configuration) |
| 135 | + |
| 136 | +3. **Key methods**: |
| 137 | + - `find(array $where, $order, $limitFrom, $limitTo, $dryRun)`: Query multiple records |
| 138 | + - `findFirst(array $where, $order, $dryRun)`: Query single record |
| 139 | + - `findByPk(...$pkValues)`: Find by primary key values |
| 140 | + - `save()`: INSERT or UPDATE based on `_isNew` state |
| 141 | + - `remove()`: DELETE the current record |
| 142 | + - `count(array $where)`: COUNT query |
| 143 | + - `exists(array $where)`: Check existence |
| 144 | + - `delete(array $where, $limit, $dryRun)`: Static delete |
| 145 | + |
| 146 | +### Query Builder Usage |
| 147 | + |
| 148 | +```php |
| 149 | +// SELECT with joins and conditions |
| 150 | +$results = query() |
| 151 | + ->select('u.id', 'u.username', 'p.title') |
| 152 | + ->from('app.users', 'u') |
| 153 | + ->leftJoin('app.posts', 'p', 'p.user_id = u.id') |
| 154 | + ->where(['u.active = ?' => 1]) |
| 155 | + ->orderBy('u.username ASC') |
| 156 | + ->limit(10) |
| 157 | + ->fetch(); |
| 158 | + |
| 159 | +// INSERT |
| 160 | +insert() |
| 161 | + ->into('app.users') |
| 162 | + ->values(['username' => 'john', 'email' => ' [email protected]']) |
| 163 | + ->execute(); |
| 164 | + |
| 165 | +// UPDATE |
| 166 | +update() |
| 167 | + ->table('app.users') |
| 168 | + ->set(['email' => ' [email protected]']) |
| 169 | + ->where(['id = ?' => 5]) |
| 170 | + ->execute(); |
| 171 | + |
| 172 | +// DELETE |
| 173 | +delete() |
| 174 | + ->from('app.users') |
| 175 | + ->where(['id = ?' => 5]) |
| 176 | + ->limit(1) |
| 177 | + ->execute(); |
| 178 | +``` |
| 179 | + |
| 180 | +## Helper Functions (src/helpers.php) |
| 181 | + |
| 182 | +These global helper functions are auto-loaded: |
| 183 | + |
| 184 | +- `db(?string $connection = null)`: Get connection instance |
| 185 | +- `query(?string $connection = null)`: Create QueryBuilder |
| 186 | +- `insert(?string $connection = null)`: Create InsertQuery |
| 187 | +- `update(?string $connection = null)`: Create UpdateQuery |
| 188 | +- `delete(?string $connection = null)`: Create DeleteQuery |
| 189 | +- `beginTransaction(?string $connection = null)`: Start transaction |
| 190 | +- `commit(?string $savepoint = null, ?string $connection = null)`: Commit transaction |
| 191 | +- `rollback(?string $savepoint = null, ?string $connection = null)`: Rollback transaction |
| 192 | +- `transaction(callable $callback, ?string $connection = null)`: Execute in transaction |
| 193 | +- `inTransaction(?string $connection = null)`: Check if in transaction |
| 194 | +- `getTransactionLevel(?string $connection = null)`: Get nesting level |
| 195 | +- `schemaAlias(string $alias, string $realSchema)`: Register schema alias |
| 196 | +- `schemaBulkAlias(array $aliases)`: Register multiple aliases |
| 197 | + |
| 198 | +## Important Implementation Details |
| 199 | + |
| 200 | +### Change Tracking in Entities |
| 201 | +- Entities use `_original` array to store initial database values |
| 202 | +- `_dirty` array tracks which fields have been modified |
| 203 | +- `detectChanges()` compares current values to `_original` |
| 204 | +- `toArray(true)` returns only dirty fields for efficient UPDATEs |
| 205 | +- `__set()` magic method automatically marks fields as dirty when modified |
| 206 | + |
| 207 | +### Dry-Run Mode |
| 208 | +Most entity methods accept a `$dryRun` parameter. When `true`, they return: |
| 209 | +```php |
| 210 | +['query' => 'SELECT ...', 'bindings' => [1, 2, 3]] |
| 211 | +``` |
| 212 | +This is useful for debugging or generating SQL without execution. |
| 213 | + |
| 214 | +### Reserved Fields in toArray() |
| 215 | +The `toArray()` method in `EntityManager` filters out fields in the `_reserved` array (`schema`, `entity`, `pk`) to prevent them from being included in INSERT/UPDATE operations. |
| 216 | + |
| 217 | +### Transaction Nesting |
| 218 | +Transactions support nesting via savepoints: |
| 219 | +- First `beginTransaction()` starts a real transaction |
| 220 | +- Nested calls create named savepoints (SAVEPOINT sp_1, sp_2, etc.) |
| 221 | +- `commit()`/`rollback()` work at the appropriate nesting level |
| 222 | +- Use `transaction(callable)` helper for automatic rollback on exceptions |
| 223 | + |
| 224 | +### Driver Registration |
| 225 | +Drivers must implement `DriverInterface`. Register them via: |
| 226 | +1. Configuration in `config/database.php` under `drivers` key |
| 227 | +2. Manually via `ConnectionManager::getInstance()->registerDriver($name, $driver)` |
| 228 | + |
| 229 | +## Namespace Convention |
| 230 | + |
| 231 | +All code uses the namespace: `PhobosFramework\Database\` |
| 232 | + |
| 233 | +Subdirectories map to sub-namespaces: |
| 234 | +- `PhobosFramework\Database\Connection\` |
| 235 | +- `PhobosFramework\Database\QueryBuilder\` |
| 236 | +- `PhobosFramework\Database\QueryBuilder\Clauses\` |
| 237 | +- `PhobosFramework\Database\Entity\` |
| 238 | +- `PhobosFramework\Database\Exceptions\` |
| 239 | +- `PhobosFramework\Database\Schema\` |
| 240 | + |
| 241 | +## Exception Hierarchy |
| 242 | + |
| 243 | +All exceptions extend base PHP exceptions with specific types: |
| 244 | +- `DatabaseException`: General database errors |
| 245 | +- `ConnectionException`: Connection-related errors |
| 246 | +- `QueryException`: Query execution errors |
| 247 | +- `TransactionException`: Transaction-related errors |
| 248 | +- `ConfigurationException`: Configuration errors |
| 249 | +- `UnsupportedDriverException`: Driver not found/supported |
| 250 | +- `InvalidArgumentException`: Invalid method arguments |
| 251 | +- `LogicException`: Logic errors (e.g., deleting unsaved entity) |
| 252 | + |
| 253 | +## Dependencies |
| 254 | + |
| 255 | +- PHP 8.3+ (uses typed properties, union types, named arguments) |
| 256 | +- `ext-pdo`: Required for database connections |
| 257 | +- `mongoose-studio/phobos-framework`: ^3.0 (parent framework) |
| 258 | + |
| 259 | +## Integration with PhobosFramework |
| 260 | + |
| 261 | +When used with PhobosFramework: |
| 262 | +1. Register `DatabaseServiceProvider` in application config |
| 263 | +2. Provider reads `config('database')` for configuration |
| 264 | +3. Services are bound to container: `db`, `db.manager`, `db.transaction` |
| 265 | +4. Access via dependency injection or `app('db')` helper |
| 266 | + |
| 267 | +## Code Style Notes |
| 268 | + |
| 269 | +- Use strict types: `declare(strict_types=1)` (not currently used but recommended) |
| 270 | +- Follow PSR-4 autoloading |
| 271 | +- Use type hints for all parameters and return types |
| 272 | +- Document complex logic with PHPDoc comments |
| 273 | +- Spanish comments are acceptable (current codebase uses Spanish) |
0 commit comments