setName('orm:mapping:describe') ->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity') ->setDescription('Display information about mapped objects') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->setHelp(<<<'EOT' The %command.full_name% command describes the metadata for the given full or partial entity class name. %command.full_name% My\Namespace\Entity\MyEntity Or: %command.full_name% MyEntity EOT); } protected function execute(InputInterface $input, OutputInterface $output): int { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $entityManager = $this->getEntityManager($input); $this->displayEntity($input->getArgument('entityName'), $entityManager, $ui); return 0; } /** * Display all the mapping information for a single Entity. * * @param string $entityName Full or partial entity class name */ private function displayEntity( string $entityName, EntityManagerInterface $entityManager, SymfonyStyle $ui, ): void { $metadata = $this->getClassMetadata($entityName, $entityManager); $ui->table( ['Field', 'Value'], array_merge( [ $this->formatField('Name', $metadata->name), $this->formatField('Root entity name', $metadata->rootEntityName), $this->formatField('Custom generator definition', $metadata->customGeneratorDefinition), $this->formatField('Custom repository class', $metadata->customRepositoryClassName), $this->formatField('Mapped super class?', $metadata->isMappedSuperclass), $this->formatField('Embedded class?', $metadata->isEmbeddedClass), $this->formatField('Parent classes', $metadata->parentClasses), $this->formatField('Sub classes', $metadata->subClasses), $this->formatField('Embedded classes', $metadata->subClasses), $this->formatField('Identifier', $metadata->identifier), $this->formatField('Inheritance type', $metadata->inheritanceType), $this->formatField('Discriminator column', $metadata->discriminatorColumn), $this->formatField('Discriminator value', $metadata->discriminatorValue), $this->formatField('Discriminator map', $metadata->discriminatorMap), $this->formatField('Generator type', $metadata->generatorType), $this->formatField('Table', $metadata->table), $this->formatField('Composite identifier?', $metadata->isIdentifierComposite), $this->formatField('Foreign identifier?', $metadata->containsForeignIdentifier), $this->formatField('Enum identifier?', $metadata->containsEnumIdentifier), $this->formatField('Sequence generator definition', $metadata->sequenceGeneratorDefinition), $this->formatField('Change tracking policy', $metadata->changeTrackingPolicy), $this->formatField('Versioned?', $metadata->isVersioned), $this->formatField('Version field', $metadata->versionField), $this->formatField('Read only?', $metadata->isReadOnly), $this->formatEntityListeners($metadata->entityListeners), ], [$this->formatField('Association mappings:', '')], $this->formatMappings($metadata->associationMappings), [$this->formatField('Field mappings:', '')], $this->formatMappings($metadata->fieldMappings), ), ); } /** * Return all mapped entity class names * * @return string[] * @psalm-return class-string[] */ private function getMappedEntities(EntityManagerInterface $entityManager): array { $entityClassNames = $entityManager->getConfiguration() ->getMetadataDriverImpl() ->getAllClassNames(); if (! $entityClassNames) { throw new InvalidArgumentException( 'You do not have any mapped Doctrine ORM entities according to the current configuration. ' . 'If you have entities or mapping files you should check your mapping configuration for errors.', ); } return $entityClassNames; } /** * Return the class metadata for the given entity * name * * @param string $entityName Full or partial entity name */ private function getClassMetadata( string $entityName, EntityManagerInterface $entityManager, ): ClassMetadata { try { return $entityManager->getClassMetadata($entityName); } catch (MappingException) { } $matches = array_filter( $this->getMappedEntities($entityManager), static fn ($mappedEntity) => preg_match('{' . preg_quote($entityName) . '}', $mappedEntity) ); if (! $matches) { throw new InvalidArgumentException(sprintf( 'Could not find any mapped Entity classes matching "%s"', $entityName, )); } if (count($matches) > 1) { throw new InvalidArgumentException(sprintf( 'Entity name "%s" is ambiguous, possible matches: "%s"', $entityName, implode(', ', $matches), )); } return $entityManager->getClassMetadata(current($matches)); } /** * Format the given value for console output */ private function formatValue(mixed $value): string { if ($value === '') { return ''; } if ($value === null) { return 'Null'; } if (is_bool($value)) { return '' . ($value ? 'True' : 'False') . ''; } if (empty($value)) { return 'Empty'; } if (is_array($value)) { return json_encode( $value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR, ); } if (is_object($value)) { return sprintf('<%s>', get_debug_type($value)); } if (is_scalar($value)) { return (string) $value; } throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true))); } /** * Add the given label and value to the two column table output * * @param string $label Label for the value * @param mixed $value A Value to show * * @return string[] * @psalm-return array{0: string, 1: string} */ private function formatField(string $label, mixed $value): array { if ($value === null) { $value = 'None'; } return [sprintf('%s', $label), $this->formatValue($value)]; } /** * Format the association mappings * * @psalm-param array $propertyMappings * * @return string[][] * @psalm-return list */ private function formatMappings(array $propertyMappings): array { $output = []; foreach ($propertyMappings as $propertyName => $mapping) { $output[] = $this->formatField(sprintf(' %s', $propertyName), ''); foreach ((array) $mapping as $field => $value) { $output[] = $this->formatField(sprintf(' %s', $field), $this->formatValue($value)); } } return $output; } /** * Format the entity listeners * * @psalm-param list $entityListeners * * @return string[] * @psalm-return array{0: string, 1: string} */ private function formatEntityListeners(array $entityListeners): array { return $this->formatField('Entity listeners', array_map('get_class', $entityListeners)); } }