Skip to content

Commit

Permalink
Merge pull request #11 from Huluti/develop
Browse files Browse the repository at this point in the history
Introduce graph format option + some code organization
  • Loading branch information
Huluti authored Jul 19, 2024
2 parents f7e1e3b + 4f2bdbc commit 067b4f9
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 72 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Add new graph-format option
- Add shortcuts for some parameters
- Add initial Unit Tests and CI

### Fixed
- Fix output path trimming

## [0.3.0] - 2024-07-19

### Changed
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,17 @@ return [

To check deletion relations of two entities in a graph:

php bin/console doctrine-relations-analyser:analyse --output data/ --graph --entities="App\\Entity\\User,App\\Entity\\Workspace" --mode="deletions"
php bin/console doctrine-relations-analyser:analyse -o data/ -g --entities="App\\Entity\\User,App\\Entity\\Workspace" -m deletions

#### Command-line Arguments

- --entities: Optional. Comma-separated list of entities to analyze.
- -m, --mode: Optional. Analysis mode: all, deletions [default: "all"]
- -o, --output: Optional. Output path for reports generated.
- -g, --graph: Optional. Generate Graphviz graph.
- -V, --version: Optional. Display help for the given command. When no command is given display help for the list command.
- -h, --help: Optional. Display this application version.
- --entities: Optional. Comma-separated list of entities to analyze
- -m, --mode: Optional. Analysis mode (all, deletions) [default: "all"]
- -o, --output: Optional. Output path for reports generated
- -g, --graph: Optional. Generate Graphviz graph
- --graph-format: Optional. Graph image format (png, svg) [default: "png"]
- -V, --version: Optional. Display help for the given command. When no command is given display help for the list command
- -h, --help: Optional. Display this application version

## Limitations

Expand Down
83 changes: 19 additions & 64 deletions src/Command/AnalyseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace DoctrineRelationsAnalyserBundle\Command;

use Doctrine\ORM\EntityManagerInterface;
use DoctrineRelationsAnalyserBundle\Enum\AnalysisMode;
use DoctrineRelationsAnalyserBundle\Enum\DeletionType;
use DoctrineRelationsAnalyserBundle\Enum\GraphFormat;
use DoctrineRelationsAnalyserBundle\Enum\Level;
use DoctrineRelationsAnalyserBundle\Service\HelperService;
use DoctrineRelationsAnalyserBundle\Service\RelationshipService;
use Graphp\Graph\Graph;
use Graphp\GraphViz\GraphViz;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -28,7 +29,7 @@
class AnalyseCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly RelationshipService $relationshipService,
private readonly Filesystem $filesystem
) {
parent::__construct();
Expand All @@ -38,9 +39,10 @@ protected function configure(): void
{
$this
->addOption('entities', null, InputOption::VALUE_REQUIRED, 'Comma-separated list of entities to analyze')
->addOption('mode', 'm', InputOption::VALUE_REQUIRED, 'Analysis mode: all, deletions', AnalysisMode::ALL->value, array_column(AnalysisMode::cases(), 'name'))
->addOption('mode', 'm', InputOption::VALUE_REQUIRED, 'Analysis mode', AnalysisMode::ALL->value, array_column(AnalysisMode::cases(), 'name'))
->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Output path for reports generated')
->addOption('graph', 'g', InputOption::VALUE_NONE, 'Generate Graphviz graph')
->addOption('graph-format', null, InputOption::VALUE_REQUIRED, 'Graph image format', GraphFormat::PNG->value, array_column(GraphFormat::cases(), 'name'))
;
}

Expand All @@ -56,7 +58,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
try {
$modeOption = AnalysisMode::from($input->getOption('mode'));
} catch (ValueError $e) {
$io->error('Invalid mode. Allowed values are: all, deletions.');
$io->error('Invalid mode. Allowed values are: ' . implode(',', array_column(AnalysisMode::cases(), 'value')));

return Command::FAILURE;
}

try {
$graphFormatOption = GraphFormat::from($input->getOption('graph-format'));
} catch (ValueError $e) {
$io->error('Invalid graph format. Allowed values are: ' . implode(',', array_column(GraphFormat::cases(), 'value')));

return Command::FAILURE;
}
Expand Down Expand Up @@ -92,61 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$entitiesToAnalyze = $entitiesOption ? explode(',', $entitiesOption) : [];
$restrictedEntities = !empty($entitiesToAnalyze);
$metaData = $this->entityManager->getMetadataFactory()->getAllMetadata();

$relationships = [];

foreach ($metaData as $meta) {
$className = $meta->getName();
if ($restrictedEntities && !in_array($className, $entitiesToAnalyze, true)) {
continue; // Skip entities not in the list
}

$relationships[$className] = [];
foreach ($meta->associationMappings as $fieldName => $association) {
$relationDetails = [
'field' => $fieldName,
'targetEntity' => $association['targetEntity'],
'type' => $association['type'],
];

if (AnalysisMode::DELETIONS === $modeOption) {
$deletions = [];

if (isset($association['orphanRemoval']) && $association['orphanRemoval']) {
$deletions[] = [
'type' => DeletionType::ORPHAN_REMOVAL,
'level' => Level::ORM,
'value' => 'true',
];
}

if (isset($association['cascade']) && in_array('remove', $association['cascade'], true)) {
$deletions[] = [
'type' => DeletionType::CASCADE,
'level' => Level::ORM,
'value' => '["remove"]',
];
}

if (!empty($association['joinColumns'])) {
if (!empty($association['joinColumns'][0]['onDelete'])) {
$deletions[] = [
'type' => DeletionType::ON_DELETE,
'level' => Level::DATABASE,
'value' => $association['joinColumns'][0]['onDelete'],
];
}
}

$relationDetails['deletions'] = $deletions;
}

$relationships[$className][] = $relationDetails;
}
}

$relationships = $this->relationshipService->fetch($entitiesToAnalyze, $modeOption);
if (empty($relationships)) {
$io->error('No relationships detected');

Expand All @@ -156,7 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->outputRelationships($relationships, $io, $modeOption);

if ($graphOption) {
if ($this->generateGraph($relationships, $outputPathOption, $modeOption)) {
if ($this->generateGraph($relationships, $outputPathOption, $modeOption, $graphFormatOption)) {
$io->success("Graph image generated in: $outputPathOption");
} else {
$io->error("Can't save graph image");
Expand Down Expand Up @@ -204,7 +160,7 @@ private function outputRelationships(array $relationships, SymfonyStyle $io, Ana
/**
* @param array<mixed> $relationships
*/
private function generateGraph(array $relationships, string $outputPath, AnalysisMode $mode): bool
private function generateGraph(array $relationships, string $outputPath, AnalysisMode $mode, GraphFormat $format): bool
{
$graph = new Graph();
$graph->setAttribute('graphviz.graph.rankdir', 'LR');
Expand Down Expand Up @@ -248,12 +204,11 @@ private function generateGraph(array $relationships, string $outputPath, Analysi
}
}

$format = 'png';
$graphviz = new GraphViz();
$graphviz->setFormat($format);
$graphviz->setFormat($format->value);
$imageData = $graphviz->createImageData($graph);

$fullPath = $outputPath . '/' . $mode->value . '.' . $format;
$fullPath = $outputPath . '/' . $mode->value . '.' . $format->value;
try {
$this->filesystem->dumpFile($fullPath, $imageData);
} catch (IOExceptionInterface) {
Expand Down
11 changes: 11 additions & 0 deletions src/Enum/GraphFormat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace DoctrineRelationsAnalyserBundle\Enum;

enum GraphFormat: string
{
case PNG = 'png';
case SVG = 'svg';
}
8 changes: 7 additions & 1 deletion src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\ORM\EntityManagerInterface;
use DoctrineRelationsAnalyserBundle\Command\AnalyseCommand;
use DoctrineRelationsAnalyserBundle\Service\HelperService;
use DoctrineRelationsAnalyserBundle\Service\RelationshipService;
use Symfony\Component\Filesystem\Filesystem;

return static function (ContainerConfigurator $container) {
Expand All @@ -15,8 +16,13 @@
$services->set(HelperService::class);

$services
->set(AnalyseCommand::class)
->set(RelationshipService::class)
->arg(0, service(EntityManagerInterface::class))
;

$services
->set(AnalyseCommand::class)
->arg(0, service(RelationshipService::class))
->arg(1, service(Filesystem::class))
->tag('console.command');
};
83 changes: 83 additions & 0 deletions src/Service/RelationshipService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

namespace DoctrineRelationsAnalyserBundle\Service;

use Doctrine\ORM\EntityManagerInterface;
use DoctrineRelationsAnalyserBundle\Enum\AnalysisMode;
use DoctrineRelationsAnalyserBundle\Enum\DeletionType;
use DoctrineRelationsAnalyserBundle\Enum\Level;

class RelationshipService
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
) {
}

/**
* @param array<string> $entities
*
* @return array<mixed>
*/
public function fetch(array $entities, AnalysisMode $mode): array
{
$restrictedEntities = !empty($entities);
$metaData = $this->entityManager->getMetadataFactory()->getAllMetadata();

$relationships = [];

foreach ($metaData as $meta) {
$className = $meta->getName();
if ($restrictedEntities && !in_array($className, $entities, true)) {
continue; // Skip entities not in the list
}

$relationships[$className] = [];
foreach ($meta->associationMappings as $fieldName => $association) {
$relationDetails = [
'field' => $fieldName,
'targetEntity' => $association['targetEntity'],
'type' => $association['type'],
];

if (AnalysisMode::DELETIONS === $mode) {
$deletions = [];

if (isset($association['orphanRemoval']) && $association['orphanRemoval']) {
$deletions[] = [
'type' => DeletionType::ORPHAN_REMOVAL,
'level' => Level::ORM,
'value' => 'true',
];
}

if (isset($association['cascade']) && in_array('remove', $association['cascade'], true)) {
$deletions[] = [
'type' => DeletionType::CASCADE,
'level' => Level::ORM,
'value' => '["remove"]',
];
}

if (!empty($association['joinColumns'])) {
if (!empty($association['joinColumns'][0]['onDelete'])) {
$deletions[] = [
'type' => DeletionType::ON_DELETE,
'level' => Level::DATABASE,
'value' => $association['joinColumns'][0]['onDelete'],
];
}
}

$relationDetails['deletions'] = $deletions;
}

$relationships[$className][] = $relationDetails;
}
}

return $relationships;
}
}

0 comments on commit 067b4f9

Please sign in to comment.