diff --git a/.gitignore b/.gitignore index 612adf8..53f0456 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -*.graphml +*.xml .idea -vendor \ No newline at end of file +vendor +.DS_Store diff --git a/ERDiagramXMLExportBundle/Command/CreateERDiagramXMLExportCommand.php b/ERDiagramXMLExportBundle/Command/CreateERDiagramXMLExportCommand.php index fbcaec5..08c8aef 100644 --- a/ERDiagramXMLExportBundle/Command/CreateERDiagramXMLExportCommand.php +++ b/ERDiagramXMLExportBundle/Command/CreateERDiagramXMLExportCommand.php @@ -1,18 +1,16 @@ setDescription('Provides an yEd-XML Output to visualize ER of Pimcore Classes'); + $this->setDescription('Provides an ER-Diagram of Pimcore Classes in XML format for diagrams.net or yEd'); + $this->addArgument('format', InputArgument::REQUIRED, 'diagrams | yed'); $this->addArgument('filename', InputArgument::OPTIONAL, 'Provide a filename'); } - public function execute(InputInterface $input, OutputInterface $output) + /** + * @throws Exception + */ + public function execute(InputInterface $input, OutputInterface $output): int { - $result = new GraphMLWriter( - $this->getClassDefinitionData(), - $this->getFieldCollectionsData(), - $this->getObjectBricksData(), - $input->getArgument('filename') ?: '' - - ); - $result->output(); - - return 0; - } - - private function getClassDefinitionData(): array - { - $listing = new ClassDefinitionListing(); - $classDefinitions = $listing->load(); - - $classDefinitionData = []; - - foreach ($classDefinitions as $classDefinition) { - $fieldDefinitions = $classDefinition->getFieldDefinitions(); - - - $data = [ - 'id' => $classDefinition->getId(), - 'name' => $classDefinition->getName(), - 'fields' => $this->processFieldDefinitions($fieldDefinitions), - 'relatedClasses' => $this->getRelatedClasses($fieldDefinitions), - 'relatedFieldCollections' => $this->getRelatedFieldCollections($fieldDefinitions), - 'relatedObjectBricks' => $this->getRelatedObjectBricks($fieldDefinitions), - ]; - - array_push($classDefinitionData, $data); - } - - return $classDefinitionData; - } - - private function processFieldDefinitions($fieldDefinitions): array - { - $data = []; - - /** @var DataObject\ClassDefinition\Data $fieldDefinition */ - foreach ($fieldDefinitions as $fieldDefinition) { - $fieldType = $fieldDefinition->getFieldtype(); - - if (strpos($fieldType, 'Relation') == false) { - $fields = [ - $fieldDefinition->getName() => $fieldType, - ]; - if ($fieldDefinition instanceof FieldCollections) { - $allowedTypes = []; - - foreach ($fieldDefinition->getAllowedTypes() as $allowedType) { - array_push($allowedTypes, $allowedType); - } - $fields = [ - $fieldDefinition->getName() => $allowedTypes, - ]; - } - array_push($data, $fields); - } - } - - return $data; - } - - private function getRelatedClasses($fieldDefinitions): array - { - $relatedClasses = []; - - /** @var DataObject\ClassDefinition\Data $fieldDefinition */ - foreach ($fieldDefinitions as $fieldDefinition) { - $fieldType = $fieldDefinition->getFieldtype(); - - if (strpos($fieldType, 'Relation') !== false) { - foreach ($fieldDefinition->getClasses() as $class) { - array_push($relatedClasses, [$fieldType => $class['classes']]); - } - } + $outputType = strtolower($input->getArgument('format')); + switch ($outputType) { + case 'diagrams': + $pimcoreDefinitionsRepository = new DiagramsPimcoreDefinitionsRepository(); + $xmlGenerator = new DiagramsXmlGenerator( + $pimcoreDefinitionsRepository->getClassDefinitionData(), + $pimcoreDefinitionsRepository->getFieldCollectionsData(), + $pimcoreDefinitionsRepository->getObjectBricksData(), + ); + break; + + case 'yed': + $pimcoreDefinitionsRepository = new YedPimcoreDefinitionsRepository(); + $xmlGenerator = new YedXmlGenerator( + $pimcoreDefinitionsRepository->getClassDefinitionData(), + $pimcoreDefinitionsRepository->getFieldCollectionsData(), + $pimcoreDefinitionsRepository->getObjectBricksData(), + ); + break; + + default: + throw new Exception('Invalid format: "' . $outputType . '". Valid: diagrams | yed'); } - return $relatedClasses; - } - - private function getRelatedFieldCollections($fieldDefinitions): array - { - $data = []; - - foreach ($fieldDefinitions as $fieldDefinition) { - if ($fieldDefinition instanceof FieldCollections) { - foreach ($fieldDefinition->getAllowedTypes() as $allowedType => $name) { - array_push($data, $name); - } - } - } + $xml = $xmlGenerator->generate(); + $this->writeToFile($input->getArgument('filename') ?: '', $xml); - return $data; + return 0; } - private function getRelatedObjectBricks($fieldDefinitions): array + /** + * @throws Exception + */ + public function writeToFile(string $fileName, string $fileContent): void { - $data = []; + $dirname = dirname(__DIR__, 5) . '/var/tmp/'; - foreach ($fieldDefinitions as $fieldDefinition) { - if ($fieldDefinition instanceof ObjectBricks) { - foreach ($fieldDefinition->getAllowedTypes() as $allowedType => $name) { - array_push($data, $name); - } - } + if (!is_dir($dirname)) { + $dirname = './var/tmp/'; } - return $data; - } - - private function getFieldCollectionsData(): array - { - $fieldCollectionData = []; - - $fieldCollectionListing = new FieldCollectionListing(); - $fieldCollections = $fieldCollectionListing->load(); - - foreach ($fieldCollections as $fieldCollection) { - $data['fieldCollection'] = [ - - 'name' => $fieldCollection->getKey(), - 'fields' => $this->processFieldDefinitions($fieldCollection->getFieldDefinitions()), - - ]; - array_push($fieldCollectionData, $data); + if (!is_dir($dirname)) { + throw new Exception('"' . $dirname . '" generate path does not exist!'); } - return $fieldCollectionData; - } - - private function getObjectBricksData(): array - { - $objectBricksData = []; - - $objectBricksListing = new ObjectBrickListing(); - $objectBricks = $objectBricksListing->load(); - - foreach ($objectBricks as $objectBrick) { - $data['objectBrick'] = [ - - 'name' => $objectBrick->getKey(), - 'fields' => $this->processFieldDefinitions($objectBrick->getFieldDefinitions()), - - ]; - array_push($objectBricksData, $data); + if (empty($fileName)) { + $file = $dirname . 'PimcoreClassDiagram.xml'; + } else { + $file = $dirname . $fileName . '.xml'; } - return $objectBricksData; + file_put_contents($file, $fileContent); } -} \ No newline at end of file +} diff --git a/ERDiagramXMLExportBundle/DependencyInjection/DiagramsPimcoreDefinitionsRepository.php b/ERDiagramXMLExportBundle/DependencyInjection/DiagramsPimcoreDefinitionsRepository.php new file mode 100644 index 0000000..4718825 --- /dev/null +++ b/ERDiagramXMLExportBundle/DependencyInjection/DiagramsPimcoreDefinitionsRepository.php @@ -0,0 +1,201 @@ +load(); + + $classDefinitionData = []; + + foreach ($classDefinitions as $classDefinition) { + $fieldDefinitions = $classDefinition->getFieldDefinitions(); + + $classDefinitionData[] = [ + 'name' => $classDefinition->getName(), + 'fields' => $this->processFieldDefinitions($fieldDefinitions, $classDefinition->getName()), + 'relationFields' => $this->getRelationFields($fieldDefinitions), + ]; + } + + return $classDefinitionData; + } + + public function getFieldCollectionsData(): array + { + $fieldCollectionData = []; + + $fieldCollectionListing = new FieldCollectionListing(); + $fieldCollections = $fieldCollectionListing->load(); + + foreach ($fieldCollections as $fieldCollection) { + $fieldCollectionData[] = [ + 'name' => $fieldCollection->getKey(), + 'fields' => $this->processFieldDefinitions($fieldCollection->getFieldDefinitions(), + $fieldCollection->getKey()), + 'relationFields' => $this->getRelationFields($fieldCollection->getFieldDefinitions()), + ]; + } + + return $fieldCollectionData; + } + + public function getObjectBricksData(): array + { + $objectBricksData = []; + + $objectBricksListing = new ObjectBrickListing(); + $objectBricks = $objectBricksListing->load(); + + foreach ($objectBricks as $objectBrick) { + $objectBricksData[] = [ + 'name' => $objectBrick->getKey(), + 'fields' => $this->processFieldDefinitions($objectBrick->getFieldDefinitions(), $objectBrick->getKey()), + 'relationFields' => $this->getRelationFields($objectBrick->getFieldDefinitions()), + ]; + } + + return $objectBricksData; + } + + /** @var Data[] $fieldDefinitions */ + private function processFieldDefinitions(array $fieldDefinitions, string $className): array + { + $fieldData = []; + + foreach ($fieldDefinitions as $fieldDefinition) { + $fieldType = $fieldDefinition->getFieldtype(); + + // todo: classification store relations +// if ($fieldDefinition instanceof Data\Classificationstore) { +// p_r($fieldDefinition); +// $fieldDefinition->getStoreId(); +// } + + if (str_contains($fieldType, 'Relation')) { + $selfRelation = !empty($fieldDefinition->getClasses()) && in_array($className, + $fieldDefinition->getClasses()[0]); + + $fieldData[] = [ + 'name' => $fieldDefinition->getName(), + 'type' => $fieldType . ($selfRelation ? ' (self)' : ''), + ]; + continue; + } + + if ($fieldDefinition instanceof FieldCollections) { + $fieldData[] = [ + 'name' => $fieldDefinition->getName(), + 'type' => implode(' | ', $fieldDefinition->getAllowedTypes()), + ]; + continue; + } + + if ($fieldDefinition instanceof Block) { + $fieldData[] = [ + 'name' => $fieldDefinition->getName(), + 'type' => $fieldDefinition->getFieldtype(), + ]; + + /** @var Data $blockField */ + foreach ($fieldDefinition->getChildren() as $blockField) { + $fieldData[] = [ + 'name' => '> ' . $blockField->getName(), + 'type' => $blockField->getFieldtype(), + ]; + } + continue; + } + + if ($fieldDefinition instanceof Localizedfields) { + // a correctly sorted order cannot be achieved. + // per class definition only the first 'localizedfields' can be accessed + // the first localized fields are always listed under 'children' in that one + // while every following localizedfields block is listed in 'referencedFields' + // p_r($fieldDefinition); // <- check it + + $localizedFields = []; + $localizedFields = array_merge($localizedFields, $fieldDefinition->getChildren()); + + /** @var Localizedfields $referencedLocalizedFields */ + $referencedLocalizedFields = $fieldDefinition->getReferencedFields(); + foreach ($referencedLocalizedFields as $referencedLocalizedField) { + $localizedFields = array_merge($localizedFields, $referencedLocalizedField->getChildren()); + } + + foreach ($localizedFields as $localizedField) { + $fieldData[] = [ + 'name' => $localizedField->getName(), + 'type' => $localizedField->getFieldType() . ' (localized)', + ]; + } + continue; + } + + $fieldData[] = [ + 'name' => $fieldDefinition->getName(), + 'type' => $fieldType, + ]; + } + return $fieldData; + } + + private function getRelationFields(array $fieldDefinitions): array + { + $relationFields = []; + + /** @var Data $fieldDefinition */ + foreach ($fieldDefinitions as $fieldDefinition) { + $fieldType = $fieldDefinition->getFieldtype(); + + if ($fieldDefinition instanceof AbstractRelations) { + foreach ($fieldDefinition->getClasses() as $class) { + $relationFields[] = [ + 'fromFieldName' => $fieldDefinition->getName(), + 'toNode' => $class['classes'], + 'toType' => DiagramsXmlGenerator::TYPE_CLASS, + 'relationType' => $fieldType, + ]; + } + } + + if ($fieldDefinition instanceof FieldCollections) { + foreach ($fieldDefinition->getAllowedTypes() as $allowedType) { + $relationFields[] = [ + 'fromFieldName' => $fieldDefinition->getName(), + 'toNode' => $allowedType, + 'toType' => DiagramsXmlGenerator::TYPE_FIELD_COLLECTION, + 'relationType' => 'OneToMany', + ]; + } + + } + + if ($fieldDefinition instanceof ObjectBricks) { + foreach ($fieldDefinition->getAllowedTypes() as $brickName) { + $relationFields[] = [ + 'fromFieldName' => $fieldDefinition->getName(), + 'toNode' => $brickName, + 'toType' => DiagramsXmlGenerator::TYPE_OBJECT_BRICK, + 'relationType' => 'OneToOne', + ]; + } + } + } + + return $relationFields; + } +} diff --git a/ERDiagramXMLExportBundle/DependencyInjection/DiagramsXmlGenerator.php b/ERDiagramXMLExportBundle/DependencyInjection/DiagramsXmlGenerator.php new file mode 100644 index 0000000..9351130 --- /dev/null +++ b/ERDiagramXMLExportBundle/DependencyInjection/DiagramsXmlGenerator.php @@ -0,0 +1,183 @@ +classDefinitions = $classDefinitions; + $this->fieldCollections = $fieldCollections; + $this->objectBricks = $objectBricks; + } + + public function generate(): string + { + $outputString = ''; + foreach ($this->classDefinitions as $classDefinition) { + $outputString .= $this->createNodesAndEdges($classDefinition, self::TYPE_CLASS); + } + foreach ($this->fieldCollections as $fieldCollection) { + $outputString .= $this->createNodesAndEdges($fieldCollection, self::TYPE_FIELD_COLLECTION); + } + foreach ($this->objectBricks as $objectBrick) { + $outputString .= $this->createNodesAndEdges($objectBrick, self::TYPE_OBJECT_BRICK); + } + $outputString .= ''; + + return $outputString; + } + + private function createNodesAndEdges(array $nodeData, string $nodeType): string + { + $node = $this->createNode($nodeData['name'], count($nodeData['fields']), $nodeType); + $attributes = $this->createAttributes($nodeData['name'], $nodeData['fields'], $nodeType); + $edges = $this->createEdges($nodeData['name'], $nodeData['relationFields'], $nodeType); + return $node . $attributes . $edges; + } + + private function createNode(string $nodeName, int $fieldCount, string $nodeType): string + { + $style = match ($nodeType) { + self::TYPE_CLASS => 'fillColor=#dae8fc;strokeColor=#6c8ebf;', // blue + self::TYPE_FIELD_COLLECTION => 'fillColor=#FFF2CC;strokeColor=#D6B656;', // yellow + self::TYPE_OBJECT_BRICK => 'fillColor=#FFE6CC;strokeColor=#D79B00;', // orange + self::TYPE_CLASSIFICATION_STORE => 'fillColor=#f8cecc;strokeColor=#b85450', // red + default => '', + }; + + $id = $nodeType . '-' . $nodeName; + $position = $this->currentNodeXPosition; + $this->currentNodeXPosition += self::NODE_WIDTH + self::NODE_MARGIN; + $nodeXml = ' + + + '; + + return sprintf( + $nodeXml, + $id, + $nodeName, + $style, + $position, + self::NODE_WIDTH, + ($fieldCount + 1) * self::ATTRIBUTE_HEIGHT, + ); + } + + private function createAttributes(string $nodeName, array $fieldData, string $nodeType): string + { + if (empty($fieldData)) { + return ''; + } + + $attributeXml = ' + + + '; + + $attributesString = ''; + + + $currentYPosition = 0; + foreach ($fieldData as $field) { + $id = $nodeType . '-' . $nodeName . '_attribute-' . $field['name']; + $parent = $nodeType . '-' . $nodeName; + $label = $field['name'] . ': ' . $field['type']; + $currentYPosition += self::ATTRIBUTE_HEIGHT; + + $attributesString .= sprintf( + $attributeXml, + $id, + $parent, + $label, + $currentYPosition, + self::NODE_WIDTH, + self::ATTRIBUTE_HEIGHT, + ); + + } + + return $attributesString; + } + + private function createEdge($from, $to, $relationType = '') + { + $sourceArrowType = self::RELATION_ONE; + $targetArrowType = self::RELATION_ONE; + + // str_contains because the relation types look like this 'manyToOneRelation', 'manyToManyObjectRelation' + if (str_contains(strtolower($relationType), 'manytomany')) { + $sourceArrowType = self::RELATION_MANY; + $targetArrowType = self::RELATION_MANY; + } else { + if (str_contains(strtolower($relationType), 'onetomany')) { + $targetArrowType = self::RELATION_MANY; + } else { + if (str_contains(strtolower($relationType), 'manytoone')) { + $sourceArrowType = self::RELATION_MANY; + } + } + } + + $id = 'edge-from_' . $from . '_edge-to_' . $to; + $edgeXml = ' + + + '; + + return sprintf( + $edgeXml, + $id, + $from, + $to, + $sourceArrowType, + $targetArrowType, + ); + } + + private function createEdges(string $parentName, array $relationFields, string $parentType): string + { + if (empty($relationFields)) { + return ''; + } + + $edges = ''; + foreach ($relationFields as $relationField) { + if ($parentName === $relationField['toNode']) { + continue; + } + + $from = $parentType . '-' . $parentName . '_attribute-' . $relationField['fromFieldName']; + $to = $relationField['toType'] . '-' . $relationField['toNode']; + $edge = $this->createEdge($from, $to, $relationField['relationType']); + $edges .= $edge; + } + + return $edges; + } +} diff --git a/ERDiagramXMLExportBundle/DependencyInjection/YedPimcoreDefinitionsRepository.php b/ERDiagramXMLExportBundle/DependencyInjection/YedPimcoreDefinitionsRepository.php new file mode 100644 index 0000000..6fd0611 --- /dev/null +++ b/ERDiagramXMLExportBundle/DependencyInjection/YedPimcoreDefinitionsRepository.php @@ -0,0 +1,162 @@ +load(); + + $classDefinitionData = []; + + foreach ($classDefinitions as $classDefinition) { + $fieldDefinitions = $classDefinition->getFieldDefinitions(); + + $data = [ + 'id' => $classDefinition->getId(), + 'name' => $classDefinition->getName(), + 'fields' => $this->processFieldDefinitions($fieldDefinitions), + 'relatedClasses' => $this->getRelatedClasses($fieldDefinitions), + 'relatedFieldCollections' => $this->getRelatedFieldCollections($fieldDefinitions), + 'relatedObjectBricks' => $this->getRelatedObjectBricks($fieldDefinitions), + ]; + + $classDefinitionData[] = $data; + } + + return $classDefinitionData; + } + + public function getFieldCollectionsData(): array + { + $fieldCollectionData = []; + + $fieldCollectionListing = new FieldCollectionListing(); + $fieldCollections = $fieldCollectionListing->load(); + + foreach ($fieldCollections as $fieldCollection) { + $data['fieldCollection'] = [ + + 'name' => $fieldCollection->getKey(), + 'fields' => $this->processFieldDefinitions($fieldCollection->getFieldDefinitions()), + + ]; + $fieldCollectionData[] = $data; + } + + return $fieldCollectionData; + } + + public function getObjectBricksData(): array + { + $objectBricksData = []; + + $objectBricksListing = new ObjectBrickListing(); + $objectBricks = $objectBricksListing->load(); + + foreach ($objectBricks as $objectBrick) { + $data['objectBrick'] = [ + + 'name' => $objectBrick->getKey(), + 'fields' => $this->processFieldDefinitions($objectBrick->getFieldDefinitions()), + + ]; + $objectBricksData[] = $data; + } + + return $objectBricksData; + } + + /** + * @param Data[] $fieldDefinitions + * @return array + */ + private function processFieldDefinitions(array $fieldDefinitions): array + { + $data = []; + + foreach ($fieldDefinitions as $fieldDefinition) { + $fieldType = $fieldDefinition->getFieldtype(); + + if (!$fieldDefinition instanceof AbstractRelations) { + $fields = [ + $fieldDefinition->getName() => $fieldType, + ]; + if ($fieldDefinition instanceof FieldCollections) { + $allowedTypes = []; + + foreach ($fieldDefinition->getAllowedTypes() as $allowedType) { + $allowedTypes[] = $allowedType; + } + $fields = [ + $fieldDefinition->getName() => $allowedTypes, + ]; + } + $data[] = $fields; + } + } + + return $data; + } + + /** + * @param Data[] $fieldDefinitions + * @return array + */ + private function getRelatedClasses(array $fieldDefinitions): array + { + $relatedClasses = []; + + foreach ($fieldDefinitions as $fieldDefinition) { + $fieldType = $fieldDefinition->getFieldtype(); + + if ($fieldDefinition instanceof AbstractRelations) { + foreach ($fieldDefinition->getClasses() as $class) { + $relatedClasses[] = [$fieldType => $class['classes']]; + } + } + } + + return $relatedClasses; + } + + private function getRelatedFieldCollections($fieldDefinitions): array + { + $data = []; + + foreach ($fieldDefinitions as $fieldDefinition) { + if ($fieldDefinition instanceof FieldCollections) { + foreach ($fieldDefinition->getAllowedTypes() as $allowedType => $name) { + $data[] = $name; + } + } + } + + return $data; + } + + private function getRelatedObjectBricks($fieldDefinitions): array + { + $data = []; + + foreach ($fieldDefinitions as $fieldDefinition) { + if ($fieldDefinition instanceof ObjectBricks) { + foreach ($fieldDefinition->getAllowedTypes() as $allowedType => $name) { + $data[] = $name; + } + } + } + + return $data; + } +} diff --git a/ERDiagramXMLExportBundle/DependencyInjection/GraphMLWriter.php b/ERDiagramXMLExportBundle/DependencyInjection/YedXmlGenerator.php similarity index 78% rename from ERDiagramXMLExportBundle/DependencyInjection/GraphMLWriter.php rename to ERDiagramXMLExportBundle/DependencyInjection/YedXmlGenerator.php index fd354d0..89c2720 100644 --- a/ERDiagramXMLExportBundle/DependencyInjection/GraphMLWriter.php +++ b/ERDiagramXMLExportBundle/DependencyInjection/YedXmlGenerator.php @@ -1,18 +1,16 @@ classDefinitions = $classDefinitions; $this->fieldCollections = $fieldCollections; $this->objectBricks = $objectBricks; - $this->filename = $filename; } - public function output() + public function generate(): string { $this->createHeader(); $this->createNodesAndEdges(); $this->createFooter(); - $this->writeToFile(); + return $this->xmlOutput; } - private function createHeader() + private function createHeader(): void { $this->xmlOutput .= " - @@ -62,7 +57,7 @@ private function createHeader() "; } - private function createNodesAndEdges() + private function createNodesAndEdges(): void { foreach ($this->classDefinitions as $classDefinition) { $this->createNode($classDefinition); @@ -76,19 +71,19 @@ private function createNodesAndEdges() if (!empty($relatedClasses)) { foreach ($relatedClasses as $class) { foreach ($class as $relationType => $className) { - $this->createEdge($parentClass, $className, $relationType, $className); + $this->createEdge($parentClass, $className, $className, $relationType); } } } if (!empty($relatedFieldCollections)) { - foreach ($relatedFieldCollections as $relatedFieldCollection => $fieldCollectionName) { - $this->createEdge($parentClass, $fieldCollectionName, 'onetomany', $fieldCollectionName); + foreach ($relatedFieldCollections as $fieldCollectionName) { + $this->createEdge($parentClass, $fieldCollectionName, $fieldCollectionName, 'onetomany'); } } if (!empty($relatedObjectBricks)) { - foreach ($relatedObjectBricks as $relatedObjectBrick => $objectBrickName) { - $this->createEdge($parentClass, $objectBrickName, '', $objectBrickName); + foreach ($relatedObjectBricks as $objectBrickName) { + $this->createEdge($parentClass, $objectBrickName, $objectBrickName, 'onetoone'); } } } @@ -106,7 +101,7 @@ private function createNodesAndEdges() } } - private function createNode(array $entry, bool $isFieldCollection = false, bool $isObjecktBrick = false) + private function createNode(array $entry, bool $isFieldCollection = false, bool $isObjecktBrick = false): void { $className = $entry['name']; $fillColor = '#E8EEF7'; @@ -120,7 +115,7 @@ private function createNode(array $entry, bool $isFieldCollection = false, bool $attributes = $this->createAttributes($entry); /* - * Sadly i cant use Spatie\ArrayToXml\ArrayToXml here because it's not possible to set an array for the _value + * Sadly I can't use Spatie\ArrayToXml\ArrayToXml here because it's not possible to set an array for the _value * Element * see: https://github.com/spatie/array-to-xml/issues/75#issuecomment-413726065 */ @@ -129,9 +124,9 @@ private function createNode(array $entry, bool $isFieldCollection = false, bool - %s %s @@ -205,23 +200,20 @@ private function createAttributes($entry): string return $arrayToXml->dropXmlDeclaration()->prettify()->toXml(); } - private function createEdge($source, $target, $relationType = '', $labelName) + private function createEdge($source, $target, $labelName, $relationType = ''): void { - if (strpos(strtolower($relationType), 'manytomany') !== false) { + $sourceArrowType = self::NONE; + $targetArrowType = self::NONE; + + if (str_contains(strtolower($relationType), 'manytomany')) { $sourceArrowType = self::CROWS_FOOT_MANY; $targetArrowType = self::CROWS_FOOT_MANY; } - if (strpos(strtolower($relationType), 'onetomany') !== false) { - $sourceArrowType = self::NONE; + if (str_contains(strtolower($relationType), 'onetomany')) { $targetArrowType = self::CROWS_FOOT_MANY; } - if (strpos(strtolower($relationType), 'manytoone') !== false) { + if (str_contains(strtolower($relationType), 'manytoone')) { $sourceArrowType = self::CROWS_FOOT_MANY; - $targetArrowType = self::NONE; - } - if ($relationType == '') { - $sourceArrowType = self::NONE; - $targetArrowType = self::NONE; } $edgeContent = " @@ -229,8 +221,8 @@ private function createEdge($source, $target, $relationType = '', $labelName) - %s @@ -238,9 +230,9 @@ private function createEdge($source, $target, $relationType = '', $labelName) - @@ -261,19 +253,6 @@ private function createEdge($source, $target, $relationType = '', $labelName) $this->xmlOutput .= $edgeContent; } - private function writeToFile() - { - $dirname = dirname(__DIR__, 5) . '/var/tmp/'; - - if (empty($this->filename)) { - $file = $dirname . 'output.graphml'; - } else { - $file = $dirname . $this->filename . '.graphml'; - } - - file_put_contents($file, $this->xmlOutput); - } - private function createFooter() { $this->xmlOutput .= " @@ -283,4 +262,4 @@ private function createFooter() "; } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 656d628..d915f38 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,68 @@ # ERDiagramXMLExportBundle -### Prerequisites and General Information +## General Information -This Pimcore-Bundle will create an yEd compliant XML File to represent the Entity Relationship of Pimcore Classes, -ObjectBricks and FieldCollections. -Just open the generated file with the yEd Graph Editor, which you can find here: -`https://www.yworks.com/products/yed/download`. -After opening the file you should use the `Layout` Tab in the Graph Editor to arrange the rendered Graphics according your needs. +This Pimcore-Bundle will create an XML File to represent the Entity Relationship of Pimcore Classes, +ObjectBricks and FieldCollections. diagrams.net (formerly draw.io) or yEd. -### Usage -``` -bin/console basilicom::create-er-diagram-xml-export -bin/console basilicom::create-er-diagram-xml-export +You can open Diagrams.net graphs with the [diagrams.net](https://diagrams.net/) editor to adjust the layout to your liking. + +For yEd graphs, you can use the [yEd Graph Editor](https://www.yworks.com/products/yed/download). +After opening the file you should use the `Layout` Tab in the Graph Editor to arrange the rendered Graphics. + +#### Diagrams.net +1. Turning DataObjects (blue), ObjectBricks (orange), and FieldCollections (yellow) and their attributes into ER Nodes +2. Recognising Localized Fields, marked as `(localized)`, and Blocks, indented with `>` +3. Turning Relations into `one to one`, `one to many`, or `many to many` ER edges - from the field where they are set to the corresponding node + +#### yEd +1. Turning DataObjects (blue), ObjectBricks (orange), and FieldCollections (yellow) and their attributes into ER Nodes +2. Turning Relations into `one to one`, `one to many`, or `many to many` ER edges from node to node +3. Automatic layout through the yEd editor + +### Limitations + +#### Diagrams.net +1. Localized Fields will need to be reordered if there are multiple blocks +(limited by the way pimcore outputs the data) +2. Classification stores are not included yet +3. The output won't be arranged + +#### yEd +1. LocalizedFields, Blocks and ClassificationStores are not included yet +2. Relations form within Object Bricks and FieldCollections are not included yet +3. Field where a relation is set do not appear in the attribute list +4. Fields are not separated + + +## Usage +```shell +bin/console basilicom:create-er-diagram-xml-export [] ``` -The generated file will be saved in the `\var\tmp\` Folder with filename`output.graphml` . -You can also provide your own filename. +The generated file will be saved in `/var/tmp/` as `PimcoreClassDiagram.xml` if no +file name is provided. + +### Capabilities + + +## Setup + #### Pimcore Configuration -Make sure to enable the Bundle in `app/config/bundles.php`, e.g. +Enable the Bundle in `/config/bundles.php` -``` +```php return [ Basilicom\ERDiagramXMLExportBundle\ERDiagramXMLExportBundle::class => ['all' => true], ]; ``` -and add it to BundleCollection in `AppKernel.php`, e.g. +And register it in `/src/Kernel.php` -``` -... +```php use Basilicom\ERDiagramXMLExportBundle\ERDiagramXMLExportBundle; -... -class AppKernel extends Kernel +class Kernel extends PimcoreKernel { /** * @param BundleCollection $collection @@ -45,3 +75,12 @@ class AppKernel extends Kernel } } ``` + +## Further development Ideas + +#### Diagrams.net +- ClassificationStore + - groups as attributes + - keys like block attributes (with >) +- Correct sorting for LocalizedFields + diff --git a/composer.json b/composer.json index 9fa9dff..3e417f8 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "basilicom/er-diagram-xml-export-bundle", - "description": "This bundle creates a yed compliant XML of the Pimcore class structure", - "keywords": ["yed","xml","er diagram"], + "description": "This bundle creates an XML of the Pimcore class structure for diagrams.net", + "keywords": ["diagrams.net","yEd","xml","er diagram","documentation","pimcore"], "type": "pimcore-bundle", "require": { "php": ">=7.4", @@ -16,6 +16,11 @@ "name": "Hendrik Hofmann", "email": "hendrik.hofmann@basilicom.de", "role": "Developer" + }, + { + "name": "Steen Helmdach", + "email": "steen.helmdach@basilicom.de", + "role": "Developer" } ], "autoload": {