Completed
Push — master ( 68be21...073c54 )
by Kévin
02:41
created

DoctrineAutoJoin   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 152
wmc 18
lcom 1
cbo 3
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 3
A getDetectedJoins() 0 4 1
C buildAccessPath() 0 67 9
A columnExists() 0 7 1
A relationExists() 0 7 1
A getRelation() 0 7 1
A embeddedExists() 0 7 2
1
<?php
2
3
namespace RulerZ\Target\DoctrineORM;
4
5
use Doctrine\ORM\EntityManagerInterface as EntityManager;
6
use Hoa\Ruler\Model as AST;
7
use RulerZ\Exception;
8
use RulerZ\Model;
9
10
class DoctrineAutoJoin
11
{
12
    /**
13
     * @var EntityManager
14
     */
15
    private $em;
16
17
    /**
18
     * @var array
19
     */
20
    private $detectedJoins = [];
21
22
    /**
23
     * @var array<Entity,alias>
24
     */
25
    private $knownEntities = [];
26
27
    /**
28
     * @var array<alias,Entity>
29
     */
30
    private $aliasMap = [];
31
32
    /**
33
     * @var array
34
     */
35
    private $rootEntities = [];
36
37
    public function __construct(EntityManager $em, array $rootEntities, array $rootAliases, array $existingJoins)
38
    {
39
        $this->em = $em;
40
        $this->rootEntities = $rootEntities;
41
42
        $this->knownEntities = array_combine($rootEntities, $rootAliases);
43
        $this->aliasMap = array_flip($this->knownEntities);
44
45
        foreach ($existingJoins as $joins) {
46
            /** @var \Doctrine\ORM\Query\Expr\Join $join */
47
            foreach ($joins as $join) {
48
                list($fromAlias, $attribute) = explode('.', $join->getJoin());
49
                $relation = $this->getRelation($attribute, $this->aliasMap[$fromAlias]);
50
51
                $this->knownEntities[$relation['targetEntity']] = $join->getAlias();
52
                $this->aliasMap[$join->getAlias()] = $relation['targetEntity'];
53
            }
54
        }
55
    }
56
57
    public function getDetectedJoins()
58
    {
59
        return $this->detectedJoins;
60
    }
61
62
    public function buildAccessPath(AST\Bag\Context $element)
63
    {
64
        $dimensionNames = array_map(function ($dimension) {
65
            return $dimension[1];
66
        }, $element->getDimensions());
67
        array_unshift($dimensionNames, $element->getId());
68
69
        $currentEntity = current($this->rootEntities);
70
71
        foreach ($dimensionNames as $i => $dimension) {
72
            if (!$this->em->getClassMetadata($currentEntity)) {
73
                throw new \Exception(sprintf('Metadata not found for entity "%s"', $currentEntity));
74
            }
75
76
            // the current dimension is an alias (a table already joined for instance)
77
            if (isset($this->aliasMap[$dimension])) {
78
                $currentEntity = $this->aliasMap[$dimension];
79
                continue;
80
            }
81
82
            // the current dimension is a relation of the current entity
83
            if ($this->relationExists($dimension, $currentEntity)) {
84
                /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
85
                $classMetadata = $this->em->getClassMetadata($currentEntity);
86
                $association = $classMetadata->getAssociationMapping($dimension);
87
88
                if (!isset($this->knownEntities[$association['targetEntity']])) {
89
                    $alias = sprintf('_%d_%s', count($this->knownEntities), $dimension);
90
91
                    $this->knownEntities[$association['targetEntity']] = $alias;
92
                    $this->aliasMap[$alias] = $association['targetEntity'];
93
                }
94
95
                $this->detectedJoins[] = [
96
                    'root' => $this->knownEntities[$currentEntity],
97
                    'column' => $dimension,
98
                    'as' => $this->knownEntities[$association['targetEntity']],
99
                ];
100
101
                $currentEntity = $association['targetEntity'];
102
                continue;
103
            }
104
105
            // the current dimension is an embedded class
106
            if ($this->embeddedExists($dimension, $currentEntity)) {
107
                /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
108
                $classMetadata = $this->em->getClassMetadata($currentEntity);
109
                $embeddableMetadata = $classMetadata->embeddedClasses[$dimension];
110
111
                $this->knownEntities[$embeddableMetadata['class']] = $this->knownEntities[$currentEntity] . '.' .$dimension;
112
                $currentEntity = $embeddableMetadata['class'];
113
114
                continue;
115
            }
116
117
            // or, at last, it's a column access.
118
            if ($this->columnExists($dimension, $currentEntity)) {
119
                if (($i + 1) === count($dimensionNames)) {
120
                    return sprintf('%s.%s', $this->knownEntities[$currentEntity], $dimension);
121
                }
122
123
                throw new \RuntimeException('Found scalar attribute in the middle of an access path.');
124
            }
125
126
            throw new \Exception(sprintf('"%s" not found for entity "%s"', $dimension, $currentEntity));
127
        }
128
    }
129
130
    private function columnExists($name, $rootEntity)
131
    {
132
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
133
        $classMetadata = $this->em->getClassMetadata($rootEntity);
134
135
        return $classMetadata->hasField($name);
136
    }
137
138
    private function relationExists($name, $rootEntity)
139
    {
140
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
141
        $classMetadata = $this->em->getClassMetadata($rootEntity);
142
143
        return $classMetadata->hasAssociation($name);
144
    }
145
146
    private function getRelation($name, $rootEntity)
147
    {
148
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
149
        $classMetadata = $this->em->getClassMetadata($rootEntity);
150
151
        return $classMetadata->getAssociationMapping($name);
152
    }
153
154
    private function embeddedExists($name, $rootEntity)
155
    {
156
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
157
        $classMetadata = $this->em->getClassMetadata($rootEntity);
158
159
        return isset($classMetadata->embeddedClasses) && isset($classMetadata->embeddedClasses[$name]);
160
    }
161
}
162