- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
9.6 KiB
Database Module New Features
This document provides an overview of the new features added to the Database module.
Table of Contents
Stored Procedures and Functions
The Database module now supports defining, executing, and managing stored procedures and functions across different database systems.
StoredProcedureDefinition
The StoredProcedureDefinition class provides a fluent interface for defining stored procedures:
use App\Framework\Database\StoredProcedure\StoredProcedureDefinition;
$procedure = StoredProcedureDefinition::create('get_user_by_id')
->withParameter('user_id', 'INT')
->withBody('SELECT * FROM users WHERE id = user_id')
->returnsType('TABLE');
The definition can then be converted to SQL for different database systems:
// Generate MySQL-specific SQL
$mysqlSql = $procedure->toSql('mysql');
// Generate PostgreSQL-specific SQL
$pgsqlSql = $procedure->toSql('pgsql');
StoredProcedureManager
The StoredProcedureManager class provides methods for managing stored procedures:
use App\Framework\Database\StoredProcedure\StoredProcedureManager;
// Create a new manager with a database connection
$manager = new StoredProcedureManager($connection);
// Create a stored procedure
$manager->createProcedure($procedure);
// Check if a procedure exists
if ($manager->procedureExists('get_user_by_id')) {
// Execute the procedure with parameters
$result = $manager->executeProcedure('get_user_by_id', [42]);
// Process the results
foreach ($result->fetchAll() as $row) {
// ...
}
}
// Execute a stored function
$value = $manager->executeFunction('calculate_order_total', [123, true]);
// Drop a procedure
$manager->dropProcedure('get_user_by_id');
// List all procedures
$procedures = $manager->listProcedures();
Extended Index Management
The Database module now supports advanced index types and features across different database systems.
AdvancedIndexDefinition
The AdvancedIndexDefinition class provides a fluent interface for defining advanced indexes:
use App\Framework\Database\Schema\Index\AdvancedIndexDefinition;
use App\Framework\Database\Schema\Index\AdvancedIndexType;
// Create a standard index
$index = AdvancedIndexDefinition::create(
'idx_users_email',
['email'],
AdvancedIndexType::INDEX
);
// Create a partial index (only indexes rows that match a condition)
$partialIndex = AdvancedIndexDefinition::partial(
'idx_active_users',
['email'],
AdvancedIndexType::INDEX,
'active = true'
);
// Create a functional index (indexes the result of a function)
$functionalIndex = AdvancedIndexDefinition::functional(
'idx_lower_email',
AdvancedIndexType::INDEX,
['LOWER(email)']
);
// Create a PostgreSQL GIN index (for full-text search, JSON, arrays)
$ginIndex = AdvancedIndexDefinition::gin(
'idx_document_content',
['content']
);
// Create a PostgreSQL GiST index (for geometric data, ranges)
$gistIndex = AdvancedIndexDefinition::gist(
'idx_location_position',
['position']
);
// Create a MySQL BTREE index with options
$btreeIndex = AdvancedIndexDefinition::btree(
'idx_users_name',
['first_name', 'last_name'],
['fillfactor' => 70]
);
The index definition can then be converted to SQL for different database systems:
// Generate SQL for PostgreSQL
$sql = $index->toSql('pgsql', 'users');
// Generate SQL for MySQL
$sql = $index->toSql('mysql', 'users');
Enhanced Schema Versioning
The Database module now includes enhanced schema versioning capabilities, including dependency tracking and schema comparison tools.
Schema Comparison
The schema comparison tools allow you to compare two database schemas and generate migration code to update one schema to match the other.
use App\Framework\Database\Schema\Comparison\SchemaComparator;
// Create a comparator with source and target connections
$comparator = new SchemaComparator($sourceConnection, $targetConnection);
// Compare the schemas
$difference = $comparator->compare();
// Check if there are differences
if ($difference->hasDifferences()) {
// Get a summary of the differences
$summary = $difference->getSummary();
// Get a detailed description of the differences
$description = $difference->getDescription();
// Generate migration code to update the target schema to match the source schema
$migrationCode = $difference->generateMigrationCode('UpdateSchema');
// Save the migration code to a file
file_put_contents('database/migrations/UpdateSchema.php', $migrationCode);
}
Dependent Migrations
The enhanced migration system now supports dependencies between migrations:
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\AbstractDependentMigration;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Schema\Schema;
final class CreateCommentsTable extends AbstractDependentMigration
{
public function getVersion(): MigrationVersion
{
return MigrationVersion::fromString('20250805123456');
}
public function getDescription(): string
{
return 'Create comments table';
}
public function getDependencies(): array
{
// This migration depends on the CreateUsersTable and CreatePostsTable migrations
return [
MigrationVersion::fromString('20250805123455'), // CreateUsersTable
MigrationVersion::fromString('20250805123454') // CreatePostsTable
];
}
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->create('comments', function ($table) {
$table->id();
$table->text('content');
$table->foreignId('user_id')->references('id')->on('users');
$table->foreignId('post_id')->references('id')->on('posts');
$table->timestamps();
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->dropIfExists('comments');
$schema->execute();
}
}
The migration runner will ensure that migrations are executed in the correct order based on their dependencies.
Usage Examples
Creating and Using a Stored Procedure
use App\Framework\Database\StoredProcedure\StoredProcedureDefinition;
use App\Framework\Database\StoredProcedure\StoredProcedureManager;
// Define a stored procedure to get active users
$procedure = StoredProcedureDefinition::create('get_active_users')
->withParameter('min_login_count', 'INT')
->withBody('
SELECT * FROM users
WHERE active = true AND login_count >= min_login_count
ORDER BY last_login DESC;
')
->returnsType('TABLE');
// Create the procedure in the database
$manager = new StoredProcedureManager($connection);
$manager->createProcedure($procedure);
// Execute the procedure
$result = $manager->executeProcedure('get_active_users', [5]);
// Process the results
foreach ($result->fetchAll() as $user) {
echo "User: {$user['name']} (Last login: {$user['last_login']})\n";
}
Creating Advanced Indexes
use App\Framework\Database\Schema\Index\AdvancedIndexDefinition;
use App\Framework\Database\Schema\Index\AdvancedIndexType;
// In a migration
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->create('posts', function ($table) {
$table->id();
$table->string('title');
$table->text('content');
$table->boolean('published')->default(false);
$table->timestamps();
});
$schema->execute();
// Create a partial index on published posts
$partialIndex = AdvancedIndexDefinition::partial(
'idx_published_posts',
['title'],
AdvancedIndexType::INDEX,
'published = true'
);
// Create a functional index for case-insensitive search
$functionalIndex = AdvancedIndexDefinition::functional(
'idx_lower_title',
AdvancedIndexType::INDEX,
['LOWER(title)']
);
// Generate and execute SQL for the current database
$driver = $connection->getPdo()->getAttribute(\PDO::ATTR_DRIVER_NAME);
$connection->execute($partialIndex->toSql($driver, 'posts'));
$connection->execute($functionalIndex->toSql($driver, 'posts'));
}
Comparing Schemas and Generating Migrations
use App\Framework\Database\Schema\Comparison\SchemaComparator;
// Compare development and production schemas
$comparator = new SchemaComparator($devConnection, $prodConnection);
$difference = $comparator->compare();
if ($difference->hasDifferences()) {
// Print a summary of the differences
$summary = $difference->getSummary();
echo "Found {$summary['total_differences']} differences:\n";
echo " Missing tables: {$summary['missing_tables']}\n";
echo " Extra tables: {$summary['extra_tables']}\n";
echo " Modified tables: {$summary['modified_tables']}\n";
// Generate migration code
$timestamp = date('YmdHis');
$className = "UpdateProductionSchema{$timestamp}";
$migrationCode = $difference->generateMigrationCode($className);
// Save the migration
$migrationPath = "database/migrations/{$className}.php";
file_put_contents($migrationPath, $migrationCode);
echo "Generated migration: {$migrationPath}\n";
}