eprofos/reverse-engineering-bundle
Advanced Symfony Bundle for Database Reverse Engineering - Automatically generate Doctrine entities from existing databases with advanced features and comprehensive testing.
Developed to simplify legacy application migration and modernization with enterprise-grade reliability.
# Install the bundle
composer require eprofos/reverse-engineering-bundle
The bundle should be automatically registered in config/bundles.php. If not, add it manually:
<?php
// config/bundles.php
return [
// ... other bundles
Eprofos\ReverseEngineeringBundle\ReverseEngineeringBundle::class => ['all' => true],
];
| Database | Version | Driver | ENUM/SET | Relations | Status |
|---|---|---|---|---|---|
| MySQL | 5.7+ | pdo_mysql | ✅ Tested | ✅ ManyToOne | ✅ Complete |
| PostgreSQL | 12+ | pdo_pgsql | ❌ Todo | ❌ Not tested | ❌ Todo |
| SQLite | 3.25+ | pdo_sqlite | ❌ Not tested | ❌ Not tested | ❌ Not tested |
Create your configuration file at config/packages/reverse_engineering.yaml:
reverse_engineering:
database:
# Database connection (required)
driver: pdo_mysql # pdo_mysql, pdo_pgsql, pdo_sqlite
host: localhost
port: 3306
dbname: your_database_name
user: your_username
password: your_password
charset: utf8mb4
generation:
# Entity generation settings
namespace: App\Entity # Namespace for generated entities
output_dir: src/Entity # Output directory for entities
generate_repository: true # Generate repository classes
use_annotations: false # Use PHP 8 attributes (recommended)
# Enum generation settings (MySQL only)
enum_namespace: App\Enum # Namespace for generated enum classes
enum_output_dir: src/Enum # Output directory for enum classes
# Table filtering
tables: [] # Specific tables to process (empty = all)
exclude_tables: # Tables to exclude
- doctrine_migration_versions
- messenger_messages
reverse_engineering:
database:
driver: pdo_pgsql
host: localhost
port: 5432
dbname: your_database_name
user: your_username
password: your_password
charset: utf8
reverse_engineering:
database:
driver: pdo_sqlite
path: '%kernel.project_dir%/var/data.db'
reverse_engineering:
database:
driver: pdo_mysql
host: '%env(DB_HOST)%'
port: '%env(int:DB_PORT)%'
dbname: '%env(DB_NAME)%'
user: '%env(DB_USER)%'
password: '%env(DB_PASSWORD)%'
charset: utf8mb4
options:
1002: "SET SESSION sql_mode=''" # PDO::MYSQL_ATTR_INIT_COMMAND
generation:
namespace: App\Entity
output_dir: src/Entity
generate_repository: true
use_annotations: false
# Enum configuration
enum_namespace: App\Enum
enum_output_dir: src/Enum
# Table filtering
tables: []
exclude_tables:
- doctrine_migration_versions
- messenger_messages
- cache_items
- sessions
# Generate all entities with default settings
php bin/console eprofos:reverse:generate
# Preview what will be generated without creating files
php bin/console eprofos:reverse:generate --dry-run --verbose
# Generate entities for specific tables
php bin/console eprofos:reverse:generate --tables=users --tables=products
# Exclude system and cache tables
php bin/console eprofos:reverse:generate --exclude=migrations --exclude=cache
# Generate with custom namespace and output directory
php bin/console eprofos:reverse:generate \
--namespace="App\Entity\Custom" \
--output-dir="src/Entity/Custom"
# Force overwrite existing entity files
php bin/console eprofos:reverse:generate --force
# User module
php bin/console eprofos:reverse:generate \
--tables=users \
--tables=user_profiles \
--namespace="App\Entity\User" \
--output-dir="src/Entity/User"
# Product module
php bin/console eprofos:reverse:generate \
--tables=products \
--tables=categories \
--namespace="App\Entity\Product" \
--output-dir="src/Entity/Product"
| Option | Short | Description | Default |
|---|---|---|---|
--tables |
-t |
Specific tables to process | All tables |
--exclude |
-e |
Tables to exclude | None |
--namespace |
-n |
Entity namespace | From config |
--output-dir |
-o |
Output directory | From config |
--force |
-f |
Force overwrite existing files | false |
--dry-run |
-d |
Preview mode (no files created) | false |
--verbose |
-v |
Verbose output | false |
Database Schema:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
first_name VARCHAR(100),
last_name VARCHAR(100),
status ENUM('active', 'inactive', 'pending') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE,
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
category_id INT NOT NULL,
status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Enum\UserStatusEnum;
use DateTimeInterface;
/**
* User entity generated automatically from database table 'users'
*/
#[ORM\Entity(repositoryClass: App\Repository\UserRepository::class)]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', nullable: false)]
private int $id;
#[ORM\Column(type: 'string', length: 255, nullable: false)]
private string $email;
#[ORM\Column(type: 'string', length: 100, nullable: true)]
private ?string $firstName = null;
#[ORM\Column(type: 'string', length: 100, nullable: true)]
private ?string $lastName = null;
#[ORM\Column(type: 'string', enumType: UserStatusEnum::class, nullable: false)]
private UserStatusEnum $status = UserStatusEnum::PENDING;
#[ORM\Column(type: 'datetime', nullable: false)]
private DateTimeInterface $createdAt;
// Getters and setters...
public function getId(): int
{
return $this->id;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getStatus(): UserStatusEnum
{
return $this->status;
}
public function setStatus(UserStatusEnum $status): static
{
$this->status = $status;
return $this;
}
// ... other getters and setters
}
<?php
declare(strict_types=1);
namespace App\Enum;
/**
* UserStatusEnum generated automatically from MySQL ENUM column
*/
enum UserStatusEnum: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case PENDING = 'pending';
}
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Enum\ProductStatusEnum;
use DateTimeInterface;
/**
* Product entity generated automatically from database table 'products'
*/
#[ORM\Entity(repositoryClass: App\Repository\ProductRepository::class)]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', nullable: false)]
private int $id;
#[ORM\Column(type: 'string', length: 255, nullable: false)]
private string $name;
#[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: false)]
private string $price;
#[ORM\Column(type: 'string', enumType: ProductStatusEnum::class, nullable: false)]
private ProductStatusEnum $status = ProductStatusEnum::DRAFT;
#[ORM\Column(type: 'datetime', nullable: false)]
private DateTimeInterface $createdAt;
#[ORM\ManyToOne(targetEntity: Category::class)]
#[ORM\JoinColumn(name: 'category_id', referencedColumnName: 'id', nullable: false)]
private Category $category;
// Getters and setters...
public function getCategory(): Category
{
return $this->category;
}
public function setCategory(Category $category): static
{
$this->category = $category;
return $this;
}
// ... other getters and setters
}
The bundle automatically generates PHP 8.1 backed enum classes from MySQL ENUM columns, providing type safety and better IDE support.
// Type-safe with IDE autocompletion
$user = new User();
$user->setStatus(UserStatusEnum::ACTIVE);
// Compile-time error prevention
$user->setStatus('invalid'); // PHP Fatal Error
// Easy value checking
if ($user->getStatus() === UserStatusEnum::ACTIVE) {
// Handle active user
}
// All enum values available
foreach (UserStatusEnum::cases() as $status) {
echo $status->value . PHP_EOL;
}
Configure enum generation in your configuration file:
reverse_engineering:
generation:
# Namespace for generated enum classes
enum_namespace: App\Enum
# Output directory for enum classes
enum_output_dir: src/Enum
| MySQL Type | PHP Type | Doctrine Type | Notes |
|---|---|---|---|
INT, INTEGER, BIGINT, SMALLINT, TINYINT |
int |
integer |
Auto-increment detection |
FLOAT, DOUBLE, REAL |
float |
float |
Precision preserved |
DECIMAL, NUMERIC |
string |
decimal |
Precision and scale preserved |
BOOLEAN, BOOL |
bool |
boolean |
Default values supported |
DATE, DATETIME, TIMESTAMP, TIME |
DateTimeInterface |
datetime |
Timezone aware |
VARCHAR, CHAR, TEXT, LONGTEXT |
string |
string |
Length constraints |
JSON |
array |
json |
Native JSON support |
BLOB, LONGBLOB |
string |
blob |
Binary data |
ENUM |
PHP 8.1 Enum |
string |
🆕 Auto-generated backed enum classes |
SET |
string |
string |
Values documented in comments |
YEAR |
int |
integer |
Year validation |
| PostgreSQL Type | PHP Type | Doctrine Type | Status |
|---|---|---|---|
INTEGER, BIGINT, SMALLINT |
int |
integer |
❌ Todo |
REAL, DOUBLE PRECISION |
float |
float |
❌ Todo |
NUMERIC, DECIMAL |
string |
decimal |
❌ Todo |
BOOLEAN |
bool |
boolean |
❌ Todo |
DATE, TIMESTAMP, TIME |
DateTimeInterface |
datetime |
❌ Todo |
VARCHAR, CHAR, TEXT |
string |
string |
❌ Todo |
JSON, JSONB |
array |
json |
❌ Todo |
UUID |
string |
guid |
❌ Todo |
ARRAY |
array |
simple_array |
❌ Todo |
| SQLite Type | PHP Type | Doctrine Type | Status |
|---|---|---|---|
INTEGER |
int |
integer |
❌ Todo |
REAL |
float |
float |
❌ Todo |
TEXT |
string |
string |
❌ Todo |
BLOB |
string |
blob |
❌ Todo |
Automatically detected from foreign key constraints in MySQL:
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
private User $user;
Feature in development - will be available in version 0.2.0
Feature planned for future release
Feature in development - will be available in version 0.2.0
Supported for hierarchical data structures:
#[ORM\ManyToOne(targetEntity: Category::class)]
#[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
private ?Category $parent = null;
# Always backup before using --force
cp -r src/Entity src/Entity.backup.$(date +%Y%m%d_%H%M%S)
php bin/console eprofos:reverse:generate --force
# Preview changes before applying
php bin/console eprofos:reverse:generate --dry-run --verbose
Configure exclusions in your configuration file:
reverse_engineering:
generation:
exclude_tables:
- doctrine_migration_versions
- messenger_messages
- cache_items
- sessions
# Use specific namespaces for organization
php bin/console eprofos:reverse:generate \
--namespace="App\Entity\User" \
--output-dir="src/Entity/User" \
--tables=users --tables=user_profiles
# Validate syntax and Doctrine mapping
find src/Entity -name "*.php" -exec php -l {} \;
php bin/console doctrine:schema:validate
The bundle provides comprehensive error handling with specific exceptions:
# Test database connection
php bin/console eprofos:reverse:generate --dry-run --tables=non_existent_table
# Check file permissions
ls -la src/Entity/
chmod 755 src/Entity/
# Increase memory limit for large databases
php -d memory_limit=512M bin/console eprofos:reverse:generate
Use verbose output for detailed information:
# Verbose output with detailed logging
php bin/console eprofos:reverse:generate -v
# Extra verbose for debugging
php bin/console eprofos:reverse:generate -vv
# Debug level output
php bin/console eprofos:reverse:generate -vvv
How can I help you explore Laravel packages today?