Completed
Push — master ( 50808e...33b518 )
by Gianluca
05:07
created

MetadataGrapher::getParent()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 2
nop 1
crap 3
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace DoctrineORMModule\Yuml;
21
22
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
23
24
/**
25
 * Utility to generate Yuml compatible strings from metadata graphs
26
 *
27
 * @license MIT
28
 * @link    http://www.doctrine-project.org/
29
 * @author  Marco Pivetta <[email protected]>
30
 */
31
class MetadataGrapher
32
{
33
    /**
34
     * Temporary array where already visited collections are stored
35
     *
36
     * @var array
37
     */
38
    protected $visitedAssociations = array();
39
40
    /**
41
     * @var \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
42
     */
43
    private $metadata;
44
45
    /**
46
     * Temporary array where reverse association name are stored
47
     *
48
     * @var \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
49
     */
50
    private $classByNames = array();
51
52
    /**
53
     * Generate a YUML compatible `dsl_text` to describe a given array
54
     * of entities
55
     *
56
     * @param  $metadata \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
57
     *
58
     * @return string
59
     */
60 21
    public function generateFromMetadata(array $metadata)
61
    {
62 21
        $this->metadata            = $metadata;
63 21
        $this->visitedAssociations = array();
64 21
        $str                       = array();
65
66 21
        foreach ($metadata as $class) {
67 21
            $parent = $this->getParent($class);
68
69 21
            if ($parent) {
70 3
                $str[] = $this->getClassString($parent) . '^' . $this->getClassString($class);
71 3
            }
72
73 21
            $associations = $class->getAssociationNames();
74
75 21
            if (empty($associations) && !isset($this->visitedAssociations[$class->getName()])) {
76 3
                $str[] = $this->getClassString($class);
77
78 3
                continue;
79
            }
80
81 19
            foreach ($associations as $associationName) {
82 17
                if ($parent && in_array($associationName, $parent->getAssociationNames())) {
83 1
                    continue;
84
                }
85
86 17
                if ($this->visitAssociation($class->getName(), $associationName)) {
87 17
                    $str[] = $this->getAssociationString($class, $associationName);
88 17
                }
89 19
            }
90 21
        }
91
92 21
        return implode(',', $str);
93
    }
94
95
    /**
96
     * @param ClassMetadata $class1
97
     * @param string $association
98
     * @return string
99
     */
100 17
    private function getAssociationString(ClassMetadata $class1, $association)
101
    {
102 17
        $targetClassName = $class1->getAssociationTargetClass($association);
103 17
        $class2          = $this->getClassByName($targetClassName);
104 17
        $isInverse       = $class1->isAssociationInverseSide($association);
105 17
        $class1Count     = $class1->isCollectionValuedAssociation($association) ? 2 : 1;
106
107 17
        if (null === $class2) {
108 2
            return $this->getClassString($class1)
109 2
                . ($isInverse ? '<' : '<>') . '-' . $association . ' '
110 2
                . ($class1Count > 1 ? '*' : ($class1Count ? '1' : ''))
111 2
                . ($isInverse ? '<>' : '>')
112 2
                . '[' . str_replace('\\', '.', $targetClassName) . ']';
113
        }
114
115 15
        $class1SideName = $association;
116 15
        $class2SideName = $this->getClassReverseAssociationName($class1, $class2);
117 15
        $class2Count    = 0;
118 15
        $bidirectional  = false;
119
120 15
        if (null !== $class2SideName) {
121 12
            if ($isInverse) {
122 5
                $class2Count    = $class2->isCollectionValuedAssociation($class2SideName) ? 2 : 1;
123 5
                $bidirectional  = true;
124 12
            } elseif ($class2->isAssociationInverseSide($class2SideName)) {
125 7
                $class2Count    = $class2->isCollectionValuedAssociation($class2SideName) ? 2 : 1;
126 7
                $bidirectional  = true;
127 7
            }
128 12
        }
129
130 15
        $this->visitAssociation($targetClassName, $class2SideName);
131
132 15
        return $this->getClassString($class1)
133 15
            . ($bidirectional ? ($isInverse ? '<' : '<>') : '') // class2 side arrow
134 15
            . ($class2SideName ? $class2SideName . ' ' : '')
135 15
            . ($class2Count > 1 ? '*' : ($class2Count ? '1' : '')) // class2 side single/multi valued
136 15
            . '-'
137 15
            . $class1SideName . ' '
138 15
            . ($class1Count > 1 ? '*' : ($class1Count ? '1' : '')) // class1 side single/multi valued
139 15
            . (($bidirectional && $isInverse) ? '<>' : '>') // class1 side arrow
140 15
            . $this->getClassString($class2);
141
    }
142
143
    /**
144
     * @param ClassMetadata $class1
145
     * @param ClassMetadata $class2
146
     * @return string|null
147
     */
148 15
    private function getClassReverseAssociationName(ClassMetadata $class1, ClassMetadata $class2)
149
    {
150 15
        foreach ($class2->getAssociationNames() as $class2Side) {
151 12
            $targetClass = $this->getClassByName($class2->getAssociationTargetClass($class2Side));
152 12
            if ($class1->getName() === $targetClass->getName()) {
153 12
                return $class2Side;
154
            }
155 9
        }
156
157 9
        return null;
158
    }
159
160
    /**
161
     * Build the string representing the single graph item
162
     *
163
     * @param ClassMetadata   $class
164
     *
165
     * @return string
166
     */
167 21
    private function getClassString(ClassMetadata $class)
168
    {
169 21
        $this->visitAssociation($class->getName());
170
171 21
        $className    = $class->getName();
172 21
        $classText    = '[' . str_replace('\\', '.', $className);
173 21
        $fields       = array();
174 21
        $parent       = $this->getParent($class);
175 21
        $parentFields = $parent ? $parent->getFieldNames() : array();
176
177 21
        foreach ($class->getFieldNames() as $fieldName) {
178 2
            if (in_array($fieldName, $parentFields)) {
179 1
                continue;
180
            }
181
182 2
            if ($class->isIdentifier($fieldName)) {
183 1
                $fields[] = '+' . $fieldName;
184 1
            } else {
185 2
                $fields[] = $fieldName;
186
            }
187 21
        }
188
189 21
        if (!empty($fields)) {
190 2
            $classText .= '|' . implode(';', $fields);
191 2
        }
192
193 21
        $classText .= ']';
194
195 21
        return $classText;
196
    }
197
198
    /**
199
     * Retrieve a class metadata instance by name from the given array
200
     *
201
     * @param string          $className
202
     *
203
     * @return ClassMetadata|null
204
     */
205 19
    private function getClassByName($className)
206
    {
207 19
        if (!isset($this->classByNames[$className])) {
208 19
            foreach ($this->metadata as $class) {
209 19
                if ($class->getName() === $className) {
210 18
                    $this->classByNames[$className] = $class;
211 18
                    break;
212
                }
213 19
            }
214 19
        }
215
216 19
        return isset($this->classByNames[$className])? $this->classByNames[$className]: null;
217
    }
218
219
    /**
220
     * Retrieve a class metadata's parent class metadata
221
     *
222
     * @param ClassMetadata   $class
223
     *
224
     * @return ClassMetadata|null
225
     */
226 21
    private function getParent($class)
227
    {
228 21
        $className = $class->getName();
229
230 21
        if (!class_exists($className) || (!$parent = get_parent_class($className))) {
231 21
            return null;
232
        }
233
234 3
        return $this->getClassByName($parent);
235
    }
236
237
    /**
238
     * Visit a given association and mark it as visited
239
     *
240
     * @param string      $className
241
     * @param string|null $association
242
     *
243
     * @return bool true if the association was visited before
244
     */
245 21
    private function visitAssociation($className, $association = null)
246
    {
247 21
        if (null === $association) {
248 21
            if (isset($this->visitedAssociations[$className])) {
249 17
                return false;
250
            }
251
252 10
            $this->visitedAssociations[$className] = array();
253
254 10
            return true;
255
        }
256
257 17
        if (isset($this->visitedAssociations[$className][$association])) {
258 12
            return false;
259
        }
260
261 17
        if (!isset($this->visitedAssociations[$className])) {
262 17
            $this->visitedAssociations[$className] = array();
263 17
        }
264
265 17
        $this->visitedAssociations[$className][$association] = true;
266
267 17
        return true;
268
    }
269
}
270