1<?php
2
3namespace Doctrine\Bundle\DoctrineBundle\Mapping;
4
5use Doctrine\ORM\Mapping\ClassMetadata;
6use Doctrine\ORM\Mapping\MappingException;
7use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
8use Doctrine\Persistence\ManagerRegistry;
9use ReflectionClass;
10use RuntimeException;
11use Symfony\Component\HttpKernel\Bundle\BundleInterface;
12
13/**
14 * This class provides methods to access Doctrine entity class metadata for a
15 * given bundle, namespace or entity class, for generation purposes
16 */
17class DisconnectedMetadataFactory
18{
19    /** @var ManagerRegistry */
20    private $registry;
21
22    /**
23     * @param ManagerRegistry $registry A ManagerRegistry instance
24     */
25    public function __construct(ManagerRegistry $registry)
26    {
27        $this->registry = $registry;
28    }
29
30    /**
31     * Gets the metadata of all classes of a bundle.
32     *
33     * @param BundleInterface $bundle A BundleInterface instance
34     *
35     * @return ClassMetadataCollection A ClassMetadataCollection instance
36     *
37     * @throws RuntimeException When bundle does not contain mapped entities.
38     */
39    public function getBundleMetadata(BundleInterface $bundle)
40    {
41        $namespace = $bundle->getNamespace();
42        $metadata  = $this->getMetadataForNamespace($namespace);
43        if (! $metadata->getMetadata()) {
44            throw new RuntimeException(sprintf('Bundle "%s" does not contain any mapped entities.', $bundle->getName()));
45        }
46
47        $path = $this->getBasePathForClass($bundle->getName(), $bundle->getNamespace(), $bundle->getPath());
48
49        $metadata->setPath($path);
50        $metadata->setNamespace($bundle->getNamespace());
51
52        return $metadata;
53    }
54
55    /**
56     * Gets the metadata of a class.
57     *
58     * @param string $class A class name
59     * @param string $path  The path where the class is stored (if known)
60     *
61     * @return ClassMetadataCollection A ClassMetadataCollection instance
62     *
63     * @throws MappingException When class is not valid entity or mapped superclass.
64     */
65    public function getClassMetadata($class, $path = null)
66    {
67        $metadata = $this->getMetadataForClass($class);
68        if (! $metadata->getMetadata()) {
69            throw MappingException::classIsNotAValidEntityOrMappedSuperClass($class);
70        }
71
72        $this->findNamespaceAndPathForMetadata($metadata, $path);
73
74        return $metadata;
75    }
76
77    /**
78     * Gets the metadata of all classes of a namespace.
79     *
80     * @param string $namespace A namespace name
81     * @param string $path      The path where the class is stored (if known)
82     *
83     * @return ClassMetadataCollection A ClassMetadataCollection instance
84     *
85     * @throws RuntimeException When namespace not contain mapped entities.
86     */
87    public function getNamespaceMetadata($namespace, $path = null)
88    {
89        $metadata = $this->getMetadataForNamespace($namespace);
90        if (! $metadata->getMetadata()) {
91            throw new RuntimeException(sprintf('Namespace "%s" does not contain any mapped entities.', $namespace));
92        }
93
94        $this->findNamespaceAndPathForMetadata($metadata, $path);
95
96        return $metadata;
97    }
98
99    /**
100     * Find and configure path and namespace for the metadata collection.
101     *
102     * @param string|null $path
103     *
104     * @throws RuntimeException When unable to determine the path.
105     */
106    public function findNamespaceAndPathForMetadata(ClassMetadataCollection $metadata, $path = null)
107    {
108        $all = $metadata->getMetadata();
109        if (class_exists($all[0]->name)) {
110            $r    = new ReflectionClass($all[0]->name);
111            $path = $this->getBasePathForClass($r->getName(), $r->getNamespaceName(), dirname($r->getFilename()));
112            $ns   = $r->getNamespaceName();
113        } elseif ($path) {
114            // Get namespace by removing the last component of the FQCN
115            $nsParts = explode('\\', $all[0]->name);
116            array_pop($nsParts);
117            $ns = implode('\\', $nsParts);
118        } else {
119            throw new RuntimeException(sprintf('Unable to determine where to save the "%s" class (use the --path option).', $all[0]->name));
120        }
121
122        $metadata->setPath($path);
123        $metadata->setNamespace($ns);
124    }
125
126    /**
127     * Get a base path for a class
128     *
129     * @param string $name      class name
130     * @param string $namespace class namespace
131     * @param string $path      class path
132     *
133     * @return string
134     *
135     * @throws RuntimeException When base path not found.
136     */
137    private function getBasePathForClass($name, $namespace, $path)
138    {
139        $namespace   = str_replace('\\', '/', $namespace);
140        $search      = str_replace('\\', '/', $path);
141        $destination = str_replace('/' . $namespace, '', $search, $c);
142
143        if ($c !== 1) {
144            throw new RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination));
145        }
146
147        return $destination;
148    }
149
150    /**
151     * @param string $namespace
152     *
153     * @return ClassMetadataCollection
154     */
155    private function getMetadataForNamespace($namespace)
156    {
157        $metadata = [];
158        foreach ($this->getAllMetadata() as $m) {
159            if (strpos($m->name, $namespace) !== 0) {
160                continue;
161            }
162
163            $metadata[] = $m;
164        }
165
166        return new ClassMetadataCollection($metadata);
167    }
168
169    /**
170     * @param string $entity
171     *
172     * @return ClassMetadataCollection
173     */
174    private function getMetadataForClass($entity)
175    {
176        foreach ($this->registry->getManagers() as $em) {
177            $cmf = new DisconnectedClassMetadataFactory();
178            $cmf->setEntityManager($em);
179
180            if (! $cmf->isTransient($entity)) {
181                return new ClassMetadataCollection([$cmf->getMetadataFor($entity)]);
182            }
183        }
184
185        return new ClassMetadataCollection([]);
186    }
187
188    /**
189     * @return ClassMetadata[]
190     */
191    private function getAllMetadata()
192    {
193        $metadata = [];
194        foreach ($this->registry->getManagers() as $em) {
195            $cmf = new DisconnectedClassMetadataFactory();
196            $cmf->setEntityManager($em);
197            foreach ($cmf->getAllMetadata() as $m) {
198                $metadata[] = $m;
199            }
200        }
201
202        return $metadata;
203    }
204}
205