From b98857eec2bc1f4cb5f6bc94d9382ca00fc20428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Poirier=20Th=C3=A9or=C3=AAt?= Date: Sun, 20 Oct 2024 15:38:18 -0400 Subject: [PATCH] [DoctrineExtra] use new draw/graphviz library --- app/src/GraphGenerator/ContextPreparator.php | 18 +- composer.json | 3 +- doc/database.svg | 1643 +++++++++-------- doc/import.svg | 123 ++ doc/user.svg | 825 +++++---- .../ORM/GraphSchema/GraphGenerator.php | 98 +- packages/doctrine-extra/composer.json | 4 + .../GenerateGraphSchemaCommandTest.php | 16 +- .../testExecute.dot | 625 +++++++ .../event_dispatcher.xml | 1 + 10 files changed, 2117 insertions(+), 1239 deletions(-) create mode 100644 doc/import.svg create mode 100644 tests/DoctrineExtra/ORM/Command/fixtures/GenerateGraphSchemaCommandTest/testExecute.dot diff --git a/app/src/GraphGenerator/ContextPreparator.php b/app/src/GraphGenerator/ContextPreparator.php index 67e88e04..85809dac 100644 --- a/app/src/GraphGenerator/ContextPreparator.php +++ b/app/src/GraphGenerator/ContextPreparator.php @@ -3,13 +3,14 @@ namespace App\GraphGenerator; use App\Entity\User; +use Draw\Bundle\SonataImportBundle\Entity\Import; use Draw\DoctrineExtra\ORM\GraphSchema\Event\PrepareContextEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; class ContextPreparator { #[AsEventListener] - public function prepareImport(PrepareContextEvent $event): void + public function prepareUser(PrepareContextEvent $event): void { $context = $event->getContext(); @@ -22,4 +23,19 @@ public function prepareImport(PrepareContextEvent $event): void ->forEntityCluster(User::class) ; } + + #[AsEventListener] + public function prepareImport(PrepareContextEvent $event): void + { + $context = $event->getContext(); + + if ('import' !== $context->getName()) { + return; + } + + $event->getContext() + ->setIgnoreAll(true) + ->forEntityCluster(Import::class) + ; + } } diff --git a/composer.json b/composer.json index 1c2f1591..8ef83744 100644 --- a/composer.json +++ b/composer.json @@ -299,7 +299,8 @@ "generate:artifact": [ "Composer\\Config::disableProcessTimeout", "bin/console draw:doctrine:generate-graph-schema | dot -Tsvg -o doc/database.svg", - "bin/console draw:doctrine:generate-graph-schema user | dot -Tsvg -o doc/user.svg" + "bin/console draw:doctrine:generate-graph-schema user | dot -Tsvg -o doc/user.svg", + "bin/console draw:doctrine:generate-graph-schema import | dot -Tsvg -o doc/import.svg" ] }, "minimum-stability": "dev", diff --git a/doc/database.svg b/doc/database.svg index 36f147fc..35f89cd9 100644 --- a/doc/database.svg +++ b/doc/database.svg @@ -4,923 +4,940 @@ - - + + draw - + -acme__user_address:se->draw_acme__user:se - - +acme__user_address:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE + + + +acme__user_tag:column_tag_id->draw_acme__tag:column_id + + + + + +acme__user_tag:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE + + + +cron_job__cron_job_execution:column_cron_job_id->cron_job__cron_job:column_id + + +on delete: CASCADE -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_child_object1_id->draw_acme__base_object:column_id + + +on delete: SET NULL -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_child_object2_id->draw_acme__base_object:column_id + + +on delete: SET NULL -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_restrict_id->draw_acme__base_object:column_id + + -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_cascade_id->draw_acme__base_object:column_id + + +on delete: CASCADE -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_set_null_id->draw_acme__base_object:column_id + + +on delete: SET NULL -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_cascade_config_overridden_id->draw_acme__base_object:column_id + + +on delete: CASCADE -draw_acme__user:se->draw_acme__base_object:se - - - - - -acme__user_tag:se->draw_acme__user:se - - +draw_acme__user:column_on_delete_cascade_attribute_overridden_id->draw_acme__base_object:column_id + + +on delete: CASCADE - - -acme__user_tag:se->draw_acme__tag:se - - - - - -cron_job__cron_job_execution:se->cron_job__cron_job:se - - + + +user_tag:column_tag_id->draw_acme__tag:column_id + + +on delete: CASCADE -user_tag:se->draw_acme__user:se - - - - - -user_tag:se->draw_acme__tag:se - - +user_tag:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE -draw_messenger__message_tag:se->draw_messenger__message:se - - +draw_messenger__message_tag:column_message_id->draw_messenger__message:column_id + + +on delete: CASCADE -draw_user__user_lock:se->draw_acme__user:se - - +draw_user__user_lock:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE -import__column:se->import__import:se - - +import__column:column_import_id->import__import:column_id + + +on delete: CASCADE -tag_translation:se->draw_acme__tag:se - - +tag_translation:column_translatable_id->draw_acme__tag:column_id + + +on delete: CASCADE -user_migration:se->draw_acme__user:se - - +user_migration:column_entity_id->draw_acme__user:column_id + + +on delete: CASCADE -user_migration:se->draw_entity_migrator__migration:se - - +user_migration:column_migration_id->draw_entity_migrator__migration:column_id + + +on delete: CASCADE acme__user_address - - -acme__user_address - -id - -integer - - - -user_id - -guid - - -position - -integer - - -address_street - -string - - -address_postal_code - -string - - -address_city - -string - - -address_country - -string - - - - - -draw_acme__user - - -draw_acme__user - -id - -guid - - - -child_object1_id - -integer - - -child_object2_id - -integer - - -on_delete_restrict_id - -integer - - -on_delete_cascade_id - -integer - - -on_delete_set_null_id - -integer - - -on_delete_cascade_config_overridden_id - -integer - - -on_delete_cascade_attribute_overridden_id - -integer - - -roles - -json - - -level - -string - - -date_of_birth - -datetime_immutable - - -comment - -text - - -preferred_locale - -string - - -email_auth_code - -string - - -email_auth_code_generated_at - -datetime_immutable - - -two_factor_authentication_enabled_providers - -json - - -force_enabling_two_factor_authentication - -boolean - - -totp_secret - -string - - -manual_lock - -boolean - - -need_change_password - -boolean - - -email - -string - - -password - -string - - -last_password_updated_at - -datetime_immutable - - -address_street - -string - - -address_postal_code - -string - - -address_city - -string - - -address_country - -string - - + + +acme__user_address + +id + +integer + + + +user_id + +guid + + +position + +integer + + +address_street + +string + + +address_postal_code + +string + + +address_city + +string + + +address_country + +string + + - + acme__user_tag - - -acme__user_tag - -id - -integer - - - -user_id - -guid - - -tag_id - -bigint - - - - - -draw_acme__tag - - -draw_acme__tag - -id - -bigint - - - -name - -string - - -active - -boolean - - + + +acme__user_tag + +id + +integer + + + +user_id + +guid + + +tag_id + +bigint + + - + command__execution - - -command__execution - -id - -guid - - - -command - -string - - -command_name - -string - - -state - -string - - -input - -json - - -output - -text - - -created_at - -datetime_immutable - - -updated_at - -datetime_immutable - - -auto_acknowledge_reason - -string - - + + +command__execution + +id + +guid + + + +command + +string + + +command_name + +string + + +state + +string + + +input + +json + + +output + +text + + +created_at + +datetime_immutable + + +updated_at + +datetime_immutable + + +auto_acknowledge_reason + +string + + - + cron_job__cron_job - - -cron_job__cron_job - -id - -integer - - - -name - -string - - -active - -boolean - - -command - -text - - -schedule - -string - - -time_to_live - -integer - - -execution_timeout - -integer - - -priority - -integer - - -notes - -text - - + + +cron_job__cron_job + +id + +integer + + + +name + +string + + +active + +boolean + + +command + +text + + +schedule + +string + + +time_to_live + +integer + + +execution_timeout + +integer + + +priority + +integer + + +notes + +text + + - + cron_job__cron_job_execution - - -cron_job__cron_job_execution - -id - -integer - - - -cron_job_id - -integer - - -requested_at - -datetime_immutable - - -state - -string - - -force - -boolean - - -execution_started_at - -datetime_immutable - - -execution_ended_at - -datetime_immutable - - -execution_delay - -integer - - -exit_code - -integer - - -error - -text - - + + +cron_job__cron_job_execution + +id + +integer + + + +cron_job_id + +integer + + +requested_at + +datetime_immutable + + +state + +string + + +force + +boolean + + +execution_started_at + +datetime_immutable + + +execution_ended_at + +datetime_immutable + + +execution_delay + +integer + + +exit_code + +integer + + +error + +text + + - + draw__config - - -draw__config - -id - -string - - - -data - -json - - -updated_at - -datetime_immutable - - -created_at - -datetime_immutable - - + + +draw__config + +id + +string + + + +data + +json + + +updated_at + +datetime_immutable + + +created_at + +datetime_immutable + + - + draw_acme__base_object - - -draw_acme__base_object - -id - -integer - - - -discriminator_type - -string - - -attribute_1 - -string - - -date_time_immutable - -datetime_immutable - - -attribute_2 - -string - - + + +draw_acme__base_object + +id + +integer + + + +discriminator_type + +string + + +attribute_1 + +string + + +date_time_immutable + +datetime_immutable + + +attribute_2 + +string + + + + + +draw_acme__tag + + +draw_acme__tag + +id + +bigint + + + +name + +string + + +active + +boolean + + + + + +draw_acme__user + + +draw_acme__user + +id + +guid + + + +child_object1_id + +integer + + +child_object2_id + +integer + + +on_delete_restrict_id + +integer + + +on_delete_cascade_id + +integer + + +on_delete_set_null_id + +integer + + +on_delete_cascade_config_overridden_id + +integer + + +on_delete_cascade_attribute_overridden_id + +integer + + +roles + +json + + +level + +string + + +date_of_birth + +datetime_immutable + + +comment + +text + + +preferred_locale + +string + + +email_auth_code + +string + + +email_auth_code_generated_at + +datetime_immutable + + +two_factor_authentication_enabled_providers + +json + + +force_enabling_two_factor_authentication + +boolean + + +totp_secret + +string + + +manual_lock + +boolean + + +need_change_password + +boolean + + +email + +string + + +password + +string + + +last_password_updated_at + +datetime_immutable + + +address_street + +string + + +address_postal_code + +string + + +address_city + +string + + +address_country + +string + + user_tag - - -user_tag - -user_id - -guid - - - -tag_id - -bigint - - - + + +user_tag + +user_id + +guid + + + +tag_id + +bigint + + + draw_entity_migrator__migration - - -draw_entity_migrator__migration - -id - -integer - - - -name - -string - - -state - -string - - + + +draw_entity_migrator__migration + +id + +integer + + + +name + +string + + +state + +string + + draw_messenger__message - - -draw_messenger__message - -id - -guid - - - -message_class - -string - - -body - -text - - -headers - -text - - -queue_name - -string - - -created_at - -datetime_immutable - - -available_at - -datetime_immutable - - -delivered_at - -datetime_immutable - - -expires_at - -datetime_immutable - - + + +draw_messenger__message + +id + +guid + + + +message_class + +string + + +body + +text + + +headers + +text + + +queue_name + +string + + +created_at + +datetime_immutable + + +available_at + +datetime_immutable + + +delivered_at + +datetime_immutable + + +expires_at + +datetime_immutable + + draw_messenger__message_tag - - -draw_messenger__message_tag - -name - -string - - - -message_id - -guid - - - + + +draw_messenger__message_tag + +name + +string + + + +message_id + +guid + + + draw_user__user_lock - - -draw_user__user_lock - -id - -guid - - - -user_id - -guid - - -reason - -string - - -created_at - -datetime_immutable - - -lock_on - -datetime_immutable - - -expires_at - -datetime_immutable - - -unlock_until - -datetime_immutable - - + + +draw_user__user_lock + +id + +guid + + + +user_id + +guid + + +reason + +string + + +created_at + +datetime_immutable + + +lock_on + +datetime_immutable + + +expires_at + +datetime_immutable + + +unlock_until + +datetime_immutable + + import__column - - -import__column - -id - -integer - - - -import_id - -integer - - -header_name - -string - - -sample - -text - - -is_identifier - -boolean - - -is_ignored - -boolean - - -mapped_to - -string - - -is_date - -boolean - - -created_at - -datetime - - -updated_at - -datetime - - + + +import__column + +id + +integer + + + +import_id + +integer + + +header_name + +string + + +sample + +text + + +is_identifier + +boolean + + +is_ignored + +boolean + + +mapped_to + +string + + +is_date + +boolean + + +created_at + +datetime + + +updated_at + +datetime + + import__import - - -import__import - -id - -integer - - - -entity_class - -string - - -insert_when_not_found - -boolean - - -file_content - -text - - -state - -string - - -created_at - -datetime - - -updated_at - -datetime - - + + +import__import + +id + +integer + + + +entity_class + +string + + +insert_when_not_found + +boolean + + +file_content + +text + + +state + +string + + +created_at + +datetime + + +updated_at + +datetime + + tag_translation - - -tag_translation - -id - -integer - - - -translatable_id - -bigint - - -label - -string - - -locale - -string - - + + +tag_translation + +id + +integer + + + +translatable_id + +bigint + + +label + +string + + +locale + +string + + user_migration - - -user_migration - -id - -bigint - - - -entity_id - -guid - - -migration_id - -integer - - -state - -string - - -transition_logs - -json - - -created_at - -datetime_immutable - - + + +user_migration + +id + +bigint + + + +entity_id + +guid + + +migration_id + +integer + + +state + +string + + +transition_logs + +json + + +created_at + +datetime_immutable + + diff --git a/doc/import.svg b/doc/import.svg new file mode 100644 index 00000000..b0f45529 --- /dev/null +++ b/doc/import.svg @@ -0,0 +1,123 @@ + + + + + + +draw + + + +import__column:column_import_id->import__import:column_id + + +on delete: CASCADE + + + +import__column + + +import__column + +id + +integer + + + +import_id + +integer + + +header_name + +string + + +sample + +text + + +is_identifier + +boolean + + +is_ignored + +boolean + + +mapped_to + +string + + +is_date + +boolean + + +created_at + +datetime + + +updated_at + +datetime + + + + + +import__import + + +import__import + +id + +integer + + + +entity_class + +string + + +insert_when_not_found + +boolean + + +file_content + +text + + +state + +string + + +created_at + +datetime + + +updated_at + +datetime + + + + + diff --git a/doc/user.svg b/doc/user.svg index ab83d643..223d9b00 100644 --- a/doc/user.svg +++ b/doc/user.svg @@ -4,479 +4,492 @@ - - + + draw - + -acme__user_address:se->draw_acme__user:se - - +acme__user_address:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE + + + +acme__user_tag:column_tag_id->draw_acme__tag:column_id + + + + + +acme__user_tag:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_child_object1_id->draw_acme__base_object:column_id + + +on delete: SET NULL -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_child_object2_id->draw_acme__base_object:column_id + + +on delete: SET NULL -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_restrict_id->draw_acme__base_object:column_id + + -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_cascade_id->draw_acme__base_object:column_id + + +on delete: CASCADE -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_set_null_id->draw_acme__base_object:column_id + + +on delete: SET NULL -draw_acme__user:se->draw_acme__base_object:se - - +draw_acme__user:column_on_delete_cascade_config_overridden_id->draw_acme__base_object:column_id + + +on delete: CASCADE -draw_acme__user:se->draw_acme__base_object:se - - - - - -acme__user_tag:se->draw_acme__user:se - - +draw_acme__user:column_on_delete_cascade_attribute_overridden_id->draw_acme__base_object:column_id + + +on delete: CASCADE - - -acme__user_tag:se->draw_acme__tag:se - - + + +user_tag:column_tag_id->draw_acme__tag:column_id + + +on delete: CASCADE -user_tag:se->draw_acme__user:se - - - - - -user_tag:se->draw_acme__tag:se - - +user_tag:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE -draw_user__user_lock:se->draw_acme__user:se - - +draw_user__user_lock:column_user_id->draw_acme__user:column_id + + +on delete: CASCADE -user_migration:se->draw_acme__user:se - - +user_migration:column_entity_id->draw_acme__user:column_id + + +on delete: CASCADE -user_migration:se->draw_entity_migrator__migration:se - - +user_migration:column_migration_id->draw_entity_migrator__migration:column_id + + +on delete: CASCADE acme__user_address - - -acme__user_address - -id - -integer - - - -user_id - -guid - - -position - -integer - - -address_street - -string - - -address_postal_code - -string - - -address_city - -string - - -address_country - -string - - + + +acme__user_address + +id + +integer + + + +user_id + +guid + + +position + +integer + + +address_street + +string + + +address_postal_code + +string + + +address_city + +string + + +address_country + +string + + - + -draw_acme__user - - -draw_acme__user - -id - -guid - - - -child_object1_id - -integer - - -child_object2_id - -integer - - -on_delete_restrict_id - -integer - - -on_delete_cascade_id - -integer - - -on_delete_set_null_id - -integer - - -on_delete_cascade_config_overridden_id - -integer - - -on_delete_cascade_attribute_overridden_id - -integer - - -roles - -json - - -level - -string - - -date_of_birth - -datetime_immutable - - -comment - -text - - -preferred_locale - -string - - -email_auth_code - -string - - -email_auth_code_generated_at - -datetime_immutable - - -two_factor_authentication_enabled_providers - -json - - -force_enabling_two_factor_authentication - -boolean - - -totp_secret - -string - - -manual_lock - -boolean - - -need_change_password - -boolean - - -email - -string - - -password - -string - - -last_password_updated_at - -datetime_immutable - - -address_street - -string - - -address_postal_code - -string - - -address_city - -string - - -address_country - -string - - +acme__user_tag + + +acme__user_tag + +id + +integer + + + +user_id + +guid + + +tag_id + +bigint + + - + -acme__user_tag - - -acme__user_tag - -id - -integer - - - -user_id - -guid - - -tag_id - -bigint - - +draw_acme__base_object + + +draw_acme__base_object + +id + +integer + + + +discriminator_type + +string + + +attribute_1 + +string + + +date_time_immutable + +datetime_immutable + + +attribute_2 + +string + + draw_acme__tag - - -draw_acme__tag - -id - -bigint - - - -name - -string - - -active - -boolean - - + + +draw_acme__tag + +id + +bigint + + + +name + +string + + +active + +boolean + + - + -draw_acme__base_object - - -draw_acme__base_object - -id - -integer - - - -discriminator_type - -string - - -attribute_1 - -string - - -date_time_immutable - -datetime_immutable - - -attribute_2 - -string - - +draw_acme__user + + +draw_acme__user + +id + +guid + + + +child_object1_id + +integer + + +child_object2_id + +integer + + +on_delete_restrict_id + +integer + + +on_delete_cascade_id + +integer + + +on_delete_set_null_id + +integer + + +on_delete_cascade_config_overridden_id + +integer + + +on_delete_cascade_attribute_overridden_id + +integer + + +roles + +json + + +level + +string + + +date_of_birth + +datetime_immutable + + +comment + +text + + +preferred_locale + +string + + +email_auth_code + +string + + +email_auth_code_generated_at + +datetime_immutable + + +two_factor_authentication_enabled_providers + +json + + +force_enabling_two_factor_authentication + +boolean + + +totp_secret + +string + + +manual_lock + +boolean + + +need_change_password + +boolean + + +email + +string + + +password + +string + + +last_password_updated_at + +datetime_immutable + + +address_street + +string + + +address_postal_code + +string + + +address_city + +string + + +address_country + +string + + user_tag - - -user_tag - -user_id - -guid - - - -tag_id - -bigint - - - + + +user_tag + +user_id + +guid + + + +tag_id + +bigint + + + draw_user__user_lock - - -draw_user__user_lock - -id - -guid - - - -user_id - -guid - - -reason - -string - - -created_at - -datetime_immutable - - -lock_on - -datetime_immutable - - -expires_at - -datetime_immutable - - -unlock_until - -datetime_immutable - - + + +draw_user__user_lock + +id + +guid + + + +user_id + +guid + + +reason + +string + + +created_at + +datetime_immutable + + +lock_on + +datetime_immutable + + +expires_at + +datetime_immutable + + +unlock_until + +datetime_immutable + + user_migration - - -user_migration - -id - -bigint - - - -entity_id - -guid - - -migration_id - -integer - - -state - -string - - -transition_logs - -json - - -created_at - -datetime_immutable - - + + +user_migration + +id + +bigint + + + +entity_id + +guid + + +migration_id + +integer + + +state + +string + + +transition_logs + +json + + +created_at + +datetime_immutable + + draw_entity_migrator__migration - -draw_entity_migrator__migration + +draw_entity_migrator__migration diff --git a/packages/doctrine-extra/ORM/GraphSchema/GraphGenerator.php b/packages/doctrine-extra/ORM/GraphSchema/GraphGenerator.php index f871e98e..050a30e7 100644 --- a/packages/doctrine-extra/ORM/GraphSchema/GraphGenerator.php +++ b/packages/doctrine-extra/ORM/GraphSchema/GraphGenerator.php @@ -2,9 +2,12 @@ namespace Draw\DoctrineExtra\ORM\GraphSchema; -use Doctrine\DBAL\Schema\Visitor\Graphviz; +use Doctrine\DBAL\Schema\Table; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Tools\SchemaTool; +use Draw\Component\Graphviz\Edge; +use Draw\Component\Graphviz\Graph; +use Draw\Component\Graphviz\Node; use Psr\EventDispatcher\EventDispatcherInterface; class GraphGenerator @@ -28,9 +31,16 @@ public function generate(Context $context): string $tool = new SchemaTool($entityManager); $schema = $tool->getSchemaFromMetadata($metadata); - $visitor = new Graphviz(); - - $visitor->acceptSchema($schema); + $graph = new Graph( + $schema->getName(), + [ + 'splines' => 'true', + 'overlap' => 'false', + 'outputorder' => 'edgesfirst', + 'mindist' => '0.6', + 'sep' => '0.2', + ] + ); $ignoreTables = $this->buildIgnoreTables($context); @@ -39,23 +49,39 @@ public function generate(Context $context): string continue; } - $visitor->acceptTable($table); - foreach ($table->getColumns() as $column) { - $visitor->acceptColumn($table, $column); - } - foreach ($table->getIndexes() as $index) { - $visitor->acceptIndex($table, $index); - } + $graph->addNode( + new Node( + $table->getName(), + [ + 'label' => $this->createTableLabel($table), + 'shape' => 'plaintext', + ] + ) + ); + foreach ($table->getForeignKeys() as $foreignKey) { - $visitor->acceptForeignKey($table, $foreignKey); + $label = []; + if ($foreignKey->onDelete()) { + $label[] = 'on delete: '.$foreignKey->onDelete(); + } + if ($foreignKey->onUpdate()) { + $label[] = 'on update: '.$foreignKey->onUpdate(); + } + $graph + ->addEdge( + new Edge( + $foreignKey->getLocalTableName().':column_'.current($foreignKey->getLocalColumns()), + $foreignKey->getForeignTableName().':column_'.current($foreignKey->getForeignColumns()), + array_filter([ + 'label' => implode("\n", $label), + ]), + ) + ) + ; } } - foreach ($schema->getSequences() as $sequence) { - $visitor->acceptSequence($sequence); - } - - return $visitor->getOutput(); + return (string) $graph; } private function buildIgnoreTables(Context $context): array @@ -102,4 +128,42 @@ private function buildIgnoreTables(Context $context): array return array_values($ignoreTables); } + + private function createTableLabel(Table $table): string + { + // Start the table + $label = << + + + TABLE; + + // The attributes block + foreach ($table->getColumns() as $column) { + $type = strtolower($column->getType()->getName()); + $primaryKeyMarker = \in_array($column->getName(), $table->getPrimaryKey()?->getColumns() ?? [], true) + ? "\xe2\x9c\xb7" + : ''; + + $label .= <<
+ {$table->getName()} +
+ + + + + TABLE; + } + + // End the table + $label .= '
+ {$column->getName()} + + {$type} + {$primaryKeyMarker}
>'; + + return $label; + } } diff --git a/packages/doctrine-extra/composer.json b/packages/doctrine-extra/composer.json index a43c9e97..fb5f6a63 100644 --- a/packages/doctrine-extra/composer.json +++ b/packages/doctrine-extra/composer.json @@ -23,8 +23,12 @@ "doctrine/collections": "^1.0", "doctrine/data-fixtures": "^1.5", "draw/dependency-injection": "^0.14", + "draw/graphviz": "^0.14", "phpunit/phpunit": "^11.3" }, + "suggest": { + "draw/graphviz": "To generate graph of the database schema" + }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { diff --git a/tests/DoctrineExtra/ORM/Command/GenerateGraphSchemaCommandTest.php b/tests/DoctrineExtra/ORM/Command/GenerateGraphSchemaCommandTest.php index 7437adee..89226f29 100644 --- a/tests/DoctrineExtra/ORM/Command/GenerateGraphSchemaCommandTest.php +++ b/tests/DoctrineExtra/ORM/Command/GenerateGraphSchemaCommandTest.php @@ -18,6 +18,8 @@ class GenerateGraphSchemaCommandTest extends KernelTestCase implements Autowired { use FilteredCommandTestTrait; + private bool $resetFile = false; + #[AutowireService(GenerateGraphSchemaCommand::class)] protected ?Command $command = null; @@ -47,11 +49,23 @@ public static function provideTestOption(): iterable */ public function testExecute(): void { - $this->execute(['context-name' => 'user']) + $display = $this->execute(['context-name' => 'user']) ->test( CommandDataTester::create() ->setExpectedDisplay(null) ) + ->getData('display') ; + + $file = __DIR__.'/fixtures/GenerateGraphSchemaCommandTest/testExecute.dot'; + + if ($this->resetFile) { + file_put_contents($file, $display); + } + + static::assertStringEqualsFile( + $file, + $display + ); } } diff --git a/tests/DoctrineExtra/ORM/Command/fixtures/GenerateGraphSchemaCommandTest/testExecute.dot b/tests/DoctrineExtra/ORM/Command/fixtures/GenerateGraphSchemaCommandTest/testExecute.dot new file mode 100644 index 00000000..47dfa1bb --- /dev/null +++ b/tests/DoctrineExtra/ORM/Command/fixtures/GenerateGraphSchemaCommandTest/testExecute.dot @@ -0,0 +1,625 @@ +digraph draw { + graph [ + splines="true", + overlap="false", + outputorder="edgesfirst", + mindist="0.6", + sep="0.2" + ]; + + acme__user_address [ + label=< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+acme__user_address +
+id + +integer +
+user_id + +guid +
+position + +integer +
+address_street + +string +
+address_postal_code + +string +
+address_city + +string +
+address_country + +string +
>, + shape="plaintext" + ]; + + acme__user_tag [ + label=< + + + + + + + + + + + + + + +
+acme__user_tag +
+id + +integer +
+user_id + +guid +
+tag_id + +bigint +
>, + shape="plaintext" + ]; + + draw_acme__base_object [ + label=< + + + + + + + + + + + + + + + + + + + + + + +
+draw_acme__base_object +
+id + +integer +
+discriminator_type + +string +
+attribute_1 + +string +
+date_time_immutable + +datetime_immutable +
+attribute_2 + +string +
>, + shape="plaintext" + ]; + + draw_acme__tag [ + label=< + + + + + + + + + + + + + + +
+draw_acme__tag +
+id + +bigint +
+name + +string +
+active + +boolean +
>, + shape="plaintext" + ]; + + draw_acme__user [ + label=< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+draw_acme__user +
+id + +guid +
+child_object1_id + +integer +
+child_object2_id + +integer +
+on_delete_restrict_id + +integer +
+on_delete_cascade_id + +integer +
+on_delete_set_null_id + +integer +
+on_delete_cascade_config_overridden_id + +integer +
+on_delete_cascade_attribute_overridden_id + +integer +
+roles + +json +
+level + +string +
+date_of_birth + +datetime_immutable +
+comment + +text +
+preferred_locale + +string +
+email_auth_code + +string +
+email_auth_code_generated_at + +datetime_immutable +
+two_factor_authentication_enabled_providers + +json +
+force_enabling_two_factor_authentication + +boolean +
+totp_secret + +string +
+manual_lock + +boolean +
+need_change_password + +boolean +
+email + +string +
+password + +string +
+last_password_updated_at + +datetime_immutable +
+address_street + +string +
+address_postal_code + +string +
+address_city + +string +
+address_country + +string +
>, + shape="plaintext" + ]; + + user_tag [ + label=< + + + + + + + + + + +
+user_tag +
+user_id + +guid +
+tag_id + +bigint +
>, + shape="plaintext" + ]; + + draw_user__user_lock [ + label=< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+draw_user__user_lock +
+id + +guid +
+user_id + +guid +
+reason + +string +
+created_at + +datetime_immutable +
+lock_on + +datetime_immutable +
+expires_at + +datetime_immutable +
+unlock_until + +datetime_immutable +
>, + shape="plaintext" + ]; + + user_migration [ + label=< + + + + + + + + + + + + + + + + + + + + + + + + + + +
+user_migration +
+id + +bigint +
+entity_id + +guid +
+migration_id + +integer +
+state + +string +
+transition_logs + +json +
+created_at + +datetime_immutable +
>, + shape="plaintext" + ]; + + acme__user_address:column_user_id -> draw_acme__user:column_id [ + label="on delete: CASCADE" + ]; + + acme__user_tag:column_user_id -> draw_acme__user:column_id [ + label="on delete: CASCADE" + ]; + + acme__user_tag:column_tag_id -> draw_acme__tag:column_id; + + draw_acme__user:column_child_object1_id -> draw_acme__base_object:column_id [ + label="on delete: SET NULL" + ]; + + draw_acme__user:column_child_object2_id -> draw_acme__base_object:column_id [ + label="on delete: SET NULL" + ]; + + draw_acme__user:column_on_delete_restrict_id -> draw_acme__base_object:column_id; + + draw_acme__user:column_on_delete_cascade_id -> draw_acme__base_object:column_id [ + label="on delete: CASCADE" + ]; + + draw_acme__user:column_on_delete_set_null_id -> draw_acme__base_object:column_id [ + label="on delete: SET NULL" + ]; + + draw_acme__user:column_on_delete_cascade_config_overridden_id -> draw_acme__base_object:column_id [ + label="on delete: CASCADE" + ]; + + draw_acme__user:column_on_delete_cascade_attribute_overridden_id -> draw_acme__base_object:column_id [ + label="on delete: CASCADE" + ]; + + user_tag:column_user_id -> draw_acme__user:column_id [ + label="on delete: CASCADE" + ]; + + user_tag:column_tag_id -> draw_acme__tag:column_id [ + label="on delete: CASCADE" + ]; + + draw_user__user_lock:column_user_id -> draw_acme__user:column_id [ + label="on delete: CASCADE" + ]; + + user_migration:column_entity_id -> draw_acme__user:column_id [ + label="on delete: CASCADE" + ]; + + user_migration:column_migration_id -> draw_entity_migrator__migration:column_id [ + label="on delete: CASCADE" + ]; +} diff --git a/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml b/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml index bb8dc50e..833af276 100644 --- a/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml +++ b/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml @@ -90,6 +90,7 @@ +