Completed
Push — master ( c8a865...42b9c6 )
by Tom
12:32 queued 12:29
created

MetadataGrapher::getClassByName()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 1
crap 4
1
<?php
2
3
namespace DoctrineORMModule\Yuml;
4
5
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
6
7
/**
8
 * Utility to generate Yuml compatible strings from metadata graphs
9
 *
10
 * @license MIT
11
 * @link    http://www.doctrine-project.org/
12
 * @author  Marco Pivetta <[email protected]>
13
 */
14
class MetadataGrapher
15
{
16
    /**
17
     * Temporary array where already visited collections are stored
18
     *
19
     * @var array
20
     */
21
    protected $visitedAssociations = [];
22
23
    /**
24
     * @var \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
25
     */
26
    private $metadata;
27
28
    /**
29
     * Temporary array where reverse association name are stored
30
     *
31
     * @var \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
32
     */
33
    private $classByNames = [];
34
35
    /**
36
     * Generate a YUML compatible `dsl_text` to describe a given array
37
     * of entities
38
     *
39
     * @param  $metadata \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
40
     *
41
     * @return string
42
     */
43 21
    public function generateFromMetadata(array $metadata)
44
    {
45 21
        $this->metadata            = $metadata;
46 21
        $this->visitedAssociations = [];
47 21
        $str                       = [];
48
49 21
        foreach ($metadata as $class) {
50 21
            $parent = $this->getParent($class);
51
52 21
            if ($parent) {
53 3
                $str[] = $this->getClassString($parent) . '^' . $this->getClassString($class);
54 3
            }
55
56 21
            $associations = $class->getAssociationNames();
57
58 21
            if (empty($associations) && ! isset($this->visitedAssociations[$class->getName()])) {
59 3
                $str[] = $this->getClassString($class);
60
61 3
                continue;
62
            }
63
64 19
            foreach ($associations as $associationName) {
65 17
                if ($parent && in_array($associationName, $parent->getAssociationNames())) {
66 1
                    continue;
67
                }
68
69 17
                if ($this->visitAssociation($class->getName(), $associationName)) {
70 17
                    $str[] = $this->getAssociationString($class, $associationName);
71 17
                }
72 19
            }
73 21
        }
74
75 21
        return implode(',', $str);
76
    }
77
78
    /**
79
     * @param ClassMetadata $class1
80
     * @param string $association
81
     * @return string
82
     */
83 17
    private function getAssociationString(ClassMetadata $class1, $association)
84
    {
85 17
        $targetClassName = $class1->getAssociationTargetClass($association);
86 17
        $class2          = $this->getClassByName($targetClassName);
87 17
        $isInverse       = $class1->isAssociationInverseSide($association);
88 17
        $class1Count     = $class1->isCollectionValuedAssociation($association) ? 2 : 1;
89
90 17
        if (null === $class2) {
91 2
            return $this->getClassString($class1)
92 2
                . ($isInverse ? '<' : '<>') . '-' . $association . ' '
93 2
                . ($class1Count > 1 ? '*' : ($class1Count ? '1' : ''))
94 2
                . ($isInverse ? '<>' : '>')
95 2
                . '[' . str_replace('\\', '.', $targetClassName) . ']';
96
        }
97
98 15
        $class1SideName = $association;
99 15
        $class2SideName = $this->getClassReverseAssociationName($class1, $class2);
100 15
        $class2Count    = 0;
101 15
        $bidirectional  = false;
102
103 15
        if (null !== $class2SideName) {
104 12
            if ($isInverse) {
105 5
                $class2Count    = $class2->isCollectionValuedAssociation($class2SideName) ? 2 : 1;
106 5
                $bidirectional  = true;
107 12
            } elseif ($class2->isAssociationInverseSide($class2SideName)) {
108 7
                $class2Count    = $class2->isCollectionValuedAssociation($class2SideName) ? 2 : 1;
109 7
                $bidirectional  = true;
110 7
            }
111 12
        }
112
113 15
        $this->visitAssociation($targetClassName, $class2SideName);
114
115 15
        return $this->getClassString($class1)
116 15
            . ($bidirectional ? ($isInverse ? '<' : '<>') : '') // class2 side arrow
117 15
            . ($class2SideName ? $class2SideName . ' ' : '')
118 15
            . ($class2Count > 1 ? '*' : ($class2Count ? '1' : '')) // class2 side single/multi valued
119 15
            . '-'
120 15
            . $class1SideName . ' '
121 15
            . ($class1Count > 1 ? '*' : ($class1Count ? '1' : '')) // class1 side single/multi valued
122 15
            . (($bidirectional && $isInverse) ? '<>' : '>') // class1 side arrow
123 15
            . $this->getClassString($class2);
124
    }
125
126
    /**
127
     * @param ClassMetadata $class1
128
     * @param ClassMetadata $class2
129
     * @return string|null
130
     */
131 15
    private function getClassReverseAssociationName(ClassMetadata $class1, ClassMetadata $class2)
132
    {
133 15
        foreach ($class2->getAssociationNames() as $class2Side) {
134 12
            $targetClass = $this->getClassByName($class2->getAssociationTargetClass($class2Side));
135 12
            if ($class1->getName() === $targetClass->getName()) {
136 12
                return $class2Side;
137
            }
138 9
        }
139
140 9
        return null;
141
    }
142
143
    /**
144
     * Build the string representing the single graph item
145
     *
146
     * @param ClassMetadata   $class
147
     *
148
     * @return string
149
     */
150 21
    private function getClassString(ClassMetadata $class)
151
    {
152 21
        $this->visitAssociation($class->getName());
153
154 21
        $className    = $class->getName();
155 21
        $classText    = '[' . str_replace('\\', '.', $className);
156 21
        $fields       = [];
157 21
        $parent       = $this->getParent($class);
158 21
        $parentFields = $parent ? $parent->getFieldNames() : [];
159
160 21
        foreach ($class->getFieldNames() as $fieldName) {
161 2
            if (in_array($fieldName, $parentFields)) {
162 1
                continue;
163
            }
164
165 2
            if ($class->isIdentifier($fieldName)) {
166 1
                $fields[] = '+' . $fieldName;
167 1
            } else {
168 2
                $fields[] = $fieldName;
169
            }
170 21
        }
171
172 21
        if (! empty($fields)) {
173 2
            $classText .= '|' . implode(';', $fields);
174 2
        }
175
176 21
        $classText .= ']';
177
178 21
        return $classText;
179
    }
180
181
    /**
182
     * Retrieve a class metadata instance by name from the given array
183
     *
184
     * @param string          $className
185
     *
186
     * @return ClassMetadata|null
187
     */
188 19
    private function getClassByName($className)
189
    {
190 19
        if (! isset($this->classByNames[$className])) {
191 19
            foreach ($this->metadata as $class) {
192 19
                if ($class->getName() === $className) {
193 18
                    $this->classByNames[$className] = $class;
194 18
                    break;
195
                }
196 19
            }
197 19
        }
198
199 19
        return $this->classByNames[$className] ?? null;
200
    }
201
202
    /**
203
     * Retrieve a class metadata's parent class metadata
204
     *
205
     * @param ClassMetadata   $class
206
     *
207
     * @return ClassMetadata|null
208
     */
209 21
    private function getParent($class)
210
    {
211 21
        $className = $class->getName();
212
213 21
        if (! class_exists($className) || (! $parent = get_parent_class($className))) {
214 21
            return null;
215
        }
216
217 3
        return $this->getClassByName($parent);
218
    }
219
220
    /**
221
     * Visit a given association and mark it as visited
222
     *
223
     * @param string      $className
224
     * @param string|null $association
225
     *
226
     * @return bool true if the association was visited before
227
     */
228 21
    private function visitAssociation($className, $association = null)
229
    {
230 21
        if (null === $association) {
231 21
            if (isset($this->visitedAssociations[$className])) {
232 17
                return false;
233
            }
234
235 10
            $this->visitedAssociations[$className] = [];
236
237 10
            return true;
238
        }
239
240 17
        if (isset($this->visitedAssociations[$className][$association])) {
241 12
            return false;
242
        }
243
244 17
        if (! isset($this->visitedAssociations[$className])) {
245 17
            $this->visitedAssociations[$className] = [];
246 17
        }
247
248 17
        $this->visitedAssociations[$className][$association] = true;
249
250 17
        return true;
251
    }
252
}
253