Skip to content

Commit 6d1e91a

Browse files
committed
refactored bundle management
Before I explain the changes, let's talk about the current state. Before this patch, the registerBundleDirs() method returned an ordered (for resource overloading) list of namespace prefixes and the path to their location. Here are some problems with this approach: * The paths set by this method and the paths configured for the autoloader can be disconnected (leading to unexpected behaviors); * A bundle outside these paths worked, but unexpected behavior can occur; * Choosing a bundle namespace was limited to the registered namespace prefixes, and their number should stay low enough (for performance reasons) -- moreover the current Bundle\ and Application\ top namespaces does not respect the standard rules for namespaces (first segment should be the vendor name); * Developers must understand the concept of "namespace prefixes" to understand the overloading mechanism, which is one more thing to learn, which is Symfony specific; * Each time you want to get a resource that can be overloaded (a template for instance), Symfony would have tried all namespace prefixes one after the other until if finds a matching file. But that can be computed in advance to reduce the overhead. Another topic which was not really well addressed is how you can reference a file/resource from a bundle (and take into account the possibility of overloading). For instance, in the routing, you can import a file from a bundle like this: <import resource="FrameworkBundle/Resources/config/internal.xml" /> Again, this works only because we have a limited number of possible namespace prefixes. This patch addresses these problems and some more. First, the registerBundleDirs() method has been removed. It means that you are now free to use any namespace for your bundles. No need to have specific prefixes anymore. You are also free to store them anywhere, in as many directories as you want. You just need to be sure that they are autoloaded correctly. The bundle "name" is now always the short name of the bundle class (like FrameworkBundle or SensioCasBundle). As the best practice is to prefix the bundle name with the vendor name, it's up to the vendor to ensure that each bundle name is unique. I insist that a bundle name must be unique. This was the opposite before as two bundles with the same name was how Symfony2 found inheritance. A new getParent() method has been added to BundleInterface. It returns the bundle name that the bundle overrides (this is optional of course). That way, there is no ordering problem anymore as the inheritance tree is explicitely defined by the bundle themselves. So, with this system, we can easily have an inheritance tree like the following: FooBundle < MyFooBundle < MyCustomFooBundle MyCustomFooBundle returns MyFooBundle for the getParent() method, and MyFooBundle returns FooBundle. If two bundles override the same bundle, an exception is thrown. Based on the bundle name, you can now reference any resource with this notation: @FooBundle/Resources/config/routing.xml @FooBundle/Controller/FooController.php This notation is the input of the Kernel::locateResource() method, which returns the location of the file (and of course it takes into account overloading). So, in the routing, you can now use the following: <import resource="@FrameworkBundle/Resources/config/internal.xml" /> The template loading mechanism also use this method under the hood. As a bonus, all the code that converts from internal notations to file names (controller names: ControllerNameParser, template names: TemplateNameParser, resource paths, ...) is now contained in several well-defined classes. The same goes for the code that look for templates (TemplateLocator), routing files (FileLocator), ... As a side note, it is really easy to also support multiple-inheritance for a bundle (for instance if a bundle returns an array of bundle names it extends). However, this is not implemented in this patch as I'm not sure we want to support that. How to upgrade: * Each bundle must now implement two new mandatory methods: getPath() and getNamespace(), and optionally the getParent() method if the bundle extends another one. Here is a common implementation for these methods: /** * {@inheritdoc} */ public function getParent() { return 'MyFrameworkBundle'; } /** * {@inheritdoc} */ public function getNamespace() { return __NAMESPACE__; } /** * {@inheritdoc} */ public function getPath() { return strtr(__DIR__, '\\', '/'); } * The registerBundleDirs() can be removed from your Kernel class; * If your code relies on getBundleDirs() or the kernel.bundle_dirs parameter, it should be upgraded to use the new interface (see Doctrine commands for many example of such a change); * When referencing a bundle, you must now always use its name (no more \ or / in bundle names) -- this transition was already done for most things before, and now applies to the routing as well; * Imports in routing files must be changed: Before: <import resource="Sensio/CasBundle/Resources/config/internal.xml" /> After: <import resource="@SensioCasBundle/Resources/config/internal.xml" />
1 parent 252918b commit 6d1e91a

File tree

92 files changed

+1403
-895
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+1403
-895
lines changed

src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,19 @@
2020
*/
2121
class CompatAssetsBundle extends Bundle
2222
{
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function getNamespace()
27+
{
28+
return __NAMESPACE__;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function getPath()
35+
{
36+
return strtr(__DIR__, '\\', '/');
37+
}
2338
}

src/Symfony/Bundle/DoctrineAbstractBundle/DependencyInjection/AbstractDoctrineExtension.php

Lines changed: 27 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,20 @@ protected function loadMappingInformation(array $objectManager, $container)
8383
}
8484

8585
if ($mappingConfig['is_bundle']) {
86-
$namespace = $this->getBundleNamespace($mappingName, $container);
87-
$mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $namespace, $mappingName, $container);
86+
$bundle = null;
87+
foreach ($container->getParameter('kernel.bundles') as $name => $class) {
88+
if ($mappingName === $name) {
89+
$bundle = new \ReflectionClass($class);
90+
91+
break;
92+
}
93+
}
94+
95+
if (null === $bundle) {
96+
throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName));
97+
}
98+
99+
$mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container);
88100
if (!$mappingConfig) {
89101
continue;
90102
}
@@ -135,92 +147,42 @@ protected function setMappingDriverConfig(array $mappingConfig, $mappingName)
135147
}
136148
}
137149

138-
/**
139-
* Finds the bundle directory for a namespace.
140-
*
141-
* If the namespace does not yield a direct match, this method will attempt
142-
* to match parent namespaces exhaustively.
143-
*
144-
* @param string $namespace A bundle namespace omitting the bundle name part
145-
* @param ContainerBuilder $container A ContainerBuilder instance
146-
*
147-
* @return string|false The bundle directory if found, false otherwise
148-
*/
149-
protected function findBundleDirForNamespace($namespace, $container)
150-
{
151-
$bundleDirs = $container->getParameter('kernel.bundle_dirs');
152-
153-
$segment = $namespace;
154-
do {
155-
if (isset($bundleDirs[$segment])) {
156-
return $bundleDirs[$segment] . str_replace('\\', '/', substr($namespace, strlen($segment)));
157-
}
158-
} while ($segment = substr($segment, 0, ($pos = strrpos($segment, '\\'))));
159-
160-
return false;
161-
}
162-
163-
/**
164-
* Get the namespace a bundle resides into.
165-
*
166-
* @param string $bundleName
167-
* @param ContainerBuilder $container
168-
* @return string
169-
*/
170-
protected function getBundleNamespace($bundleName, $container)
171-
{
172-
foreach ($container->getParameter('kernel.bundles') AS $bundleClassName) {
173-
$tmp = dirname(str_replace('\\', '/', $bundleClassName));
174-
$namespace = str_replace('/', '\\', dirname($tmp));
175-
$actualBundleName = basename($tmp);
176-
177-
if ($actualBundleName == $bundleName) {
178-
return $namespace;
179-
}
180-
}
181-
return null;
182-
}
183-
184150
/**
185151
* If this is a bundle controlled mapping all the missing information can be autodetected by this method.
186152
*
187153
* Returns false when autodetection failed, an array of the completed information otherwise.
188154
*
189-
* @param array $bundleConfig
190-
* @param string $namespace
191-
* @param string $bundleName
192-
* @param Container $container
155+
* @param array $bundleConfig
156+
* @param \ReflectionClass $bundle
157+
* @param Container $container
158+
*
193159
* @return array|false
194160
*/
195-
protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, $namespace, $bundleName, $container)
161+
protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, $container)
196162
{
197-
$bundleDir = $this->findBundleDirForNamespace($namespace, $container);
198-
199-
if (!$bundleDir) {
200-
// skip this bundle if we cannot find its location, it must be misspelled or something.
201-
return false;
202-
}
163+
$bundleDir = dirname($bundle->getFilename());
203164

204165
if (!$bundleConfig['type']) {
205-
$bundleConfig['type'] = $this->detectMetadataDriver($bundleDir.'/'.$bundleName, $container);
166+
$bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container);
206167
}
168+
207169
if (!$bundleConfig['type']) {
208170
// skip this bundle, no mapping information was found.
209171
return false;
210172
}
211173

212174
if (!$bundleConfig['dir']) {
213175
if (in_array($bundleConfig['type'], array('annotation', 'static-php'))) {
214-
$bundleConfig['dir'] = $bundleDir.'/'.$bundleName.'/' . $this->getMappingObjectDefaultName();
176+
$bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingObjectDefaultName();
215177
} else {
216-
$bundleConfig['dir'] = $bundleDir.'/'.$bundleName.'/' . $this->getMappingResourceConfigDirectory();
178+
$bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory();
217179
}
218180
} else {
219-
$bundleConfig['dir'] = $bundleDir.'/'.$bundleName.'/' . $bundleConfig['dir'];
181+
$bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir'];
220182
}
221183

222184
if (!$bundleConfig['prefix']) {
223-
$bundleConfig['prefix'] = $namespace.'\\'. $bundleName . '\\' . $this->getMappingObjectDefaultName();
185+
$bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName();
224186
}
225187
return $bundleConfig;
226188
}
@@ -325,7 +287,7 @@ protected function detectMetadataDriver($dir, ContainerBuilder $container)
325287
// add the directory itself as a resource
326288
$container->addResource(new FileResource($dir));
327289

328-
if (is_dir($dir . '/' . $this->getMappingObjectDefaultName())) {
290+
if (is_dir($dir.'/'.$this->getMappingObjectDefaultName())) {
329291
return 'annotation';
330292
}
331293

src/Symfony/Bundle/DoctrineBundle/Command/ConvertDoctrine1SchemaDoctrineCommand.php

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ protected function configure()
4343
->setHelp(<<<EOT
4444
The <info>doctrine:mapping:convert-d1-schema</info> command converts a Doctrine 1 schema to Doctrine 2 mapping files:
4545
46-
<info>./app/console doctrine:mapping:convert-d1-schema /path/to/doctrine1schema "Bundle\MyBundle" xml</info>
46+
<info>./app/console doctrine:mapping:convert-d1-schema /path/to/doctrine1schema "BundleMyBundle" xml</info>
4747
4848
Each Doctrine 1 model will have its own XML mapping file located in <info>Bundle/MyBundle/config/doctrine/metadata</info>.
4949
EOT
@@ -55,22 +55,9 @@ protected function configure()
5555
*/
5656
protected function execute(InputInterface $input, OutputInterface $output)
5757
{
58-
$bundleClass = null;
59-
$bundleDirs = $this->container->get('kernel')->getBundleDirs();
60-
foreach ($this->container->get('kernel')->getBundles() as $bundle) {
61-
if (strpos(get_class($bundle), $input->getArgument('bundle')) !== false) {
62-
$tmp = dirname(str_replace('\\', '/', get_class($bundle)));
63-
$namespace = str_replace('/', '\\', dirname($tmp));
64-
$class = basename($tmp);
65-
66-
if (isset($bundleDirs[$namespace])) {
67-
$destPath = realpath($bundleDirs[$namespace]).'/'.$class;
68-
$bundleClass = $class;
69-
break;
70-
}
71-
}
72-
}
58+
$bundle = $this->application->getKernel()->getBundle($input->getArgument('bundle'));
7359

60+
$destPath = $bundle->getPath();
7461
$type = $input->getArgument('mapping-type') ? $input->getArgument('mapping-type') : 'xml';
7562
if ('annotation' === $type) {
7663
$destPath .= '/Entity';
@@ -98,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
9885
$output->writeln(sprintf('Converting Doctrine 1 schema "<info>%s</info>"', $input->getArgument('d1-schema')));
9986
foreach ($metadata as $class) {
10087
$className = $class->name;
101-
$class->name = $namespace.'\\'.$bundleClass.'\\Entity\\'.$className;
88+
$class->name = $bundle->getNamespace().'\\Entity\\'.$className;
10289
if ('annotation' === $type) {
10390
$path = $destPath.'/'.$className.'.php';
10491
} else {

src/Symfony/Bundle/DoctrineBundle/Command/GenerateEntitiesDoctrineCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
6262
}
6363

6464
$entityGenerator = $this->getEntityGenerator();
65-
foreach ($this->container->get('kernel')->getBundles() as $bundle) {
65+
foreach ($this->application->getKernel()->getBundles() as $bundle) {
6666

6767
// retrieve the full bundle classname
68-
$class = $bundle->getReflection()->getName();
68+
$class = get_class($bundle);
6969

7070
if ($filterBundle && $filterBundle != $class) {
7171
continue;

src/Symfony/Bundle/DoctrineBundle/Command/GenerateEntityDoctrineCommand.php

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ protected function configure()
4040
->setHelp(<<<EOT
4141
The <info>doctrine:generate:entity</info> task initializes a new Doctrine entity inside a bundle:
4242
43-
<info>./app/console doctrine:generate:entity "Bundle\MyCustomBundle" "User\Group"</info>
43+
<info>./app/console doctrine:generate:entity "MyCustomBundle" "User\Group"</info>
4444
4545
The above would initialize a new entity in the following entity namespace <info>Bundle\MyCustomBundle\Entity\User\Group</info>.
4646
4747
You can also optionally specify the fields you want to generate in the new entity:
4848
49-
<info>./app/console doctrine:generate:entity "Bundle\MyCustomBundle" "User\Group" --fields="name:string(255) description:text"</info>
49+
<info>./app/console doctrine:generate:entity "MyCustomBundle" "User\Group" --fields="name:string(255) description:text"</info>
5050
EOT
5151
);
5252
}
@@ -56,23 +56,10 @@ protected function configure()
5656
*/
5757
protected function execute(InputInterface $input, OutputInterface $output)
5858
{
59-
if (!preg_match('/Bundle$/', $bundle = $input->getArgument('bundle'))) {
60-
throw new \InvalidArgumentException('The bundle name must end with Bundle. Example: "Bundle\MySampleBundle".');
61-
}
62-
63-
$dirs = $this->container->get('kernel')->getBundleDirs();
64-
65-
$tmp = str_replace('\\', '/', $bundle);
66-
$namespace = str_replace('/', '\\', dirname($tmp));
67-
$bundle = basename($tmp);
68-
69-
if (!isset($dirs[$namespace])) {
70-
throw new \InvalidArgumentException(sprintf('Unable to initialize the bundle entity (%s not defined).', $namespace));
71-
}
59+
$bundle = $this->application->getKernel()->getBundle($input->getArgument('bundle'));
7260

7361
$entity = $input->getArgument('entity');
74-
$entityNamespace = $namespace.'\\'.$bundle.'\\Entity';
75-
$fullEntityClassName = $entityNamespace.'\\'.$entity;
62+
$fullEntityClassName = $bundle->getNamespace().'\\Entity\\'.$entity;
7663
$mappingType = $input->getOption('mapping-type');
7764

7865
$class = new ClassMetadataInfo($fullEntityClassName);
@@ -103,22 +90,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
10390
$exporter = $cme->getExporter($mappingType);
10491

10592
if ('annotation' === $mappingType) {
106-
$path = $dirs[$namespace].'/'.$bundle.'/Entity/'.str_replace($entityNamespace.'\\', null, $fullEntityClassName).'.php';
107-
93+
$path = $bundle->getPath().'/Entity/'.$entity.'.php';
10894
$exporter->setEntityGenerator($this->getEntityGenerator());
10995
} else {
11096
$mappingType = 'yaml' == $mappingType ? 'yml' : $mappingType;
111-
$path = $dirs[$namespace].'/'.$bundle.'/Resources/config/doctrine/metadata/orm/'.str_replace('\\', '.', $fullEntityClassName).'.dcm.'.$mappingType;
97+
$path = $bundle->getPath().'/Resources/config/doctrine/metadata/orm/'.str_replace('\\', '.', $fullEntityClassName).'.dcm.'.$mappingType;
11298
}
11399

114100
$code = $exporter->exportClassMetadata($class);
115101

102+
$output->writeln(sprintf('Generating entity for "<info>%s</info>"', $bundle->getName()));
103+
$output->writeln(sprintf(' > generating <comment>%s</comment>', $fullEntityClassName));
104+
116105
if (!is_dir($dir = dirname($path))) {
117106
mkdir($dir, 0777, true);
118107
}
119-
120-
$output->writeln(sprintf('Generating entity for "<info>%s</info>"', $bundle));
121-
$output->writeln(sprintf(' > generating <comment>%s</comment>', $fullEntityClassName));
122108
file_put_contents($path, $code);
123109
}
124110
}

src/Symfony/Bundle/DoctrineBundle/Command/GenerateRepositoriesDoctrineCommand.php

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,14 @@ protected function configure()
4040
protected function execute(InputInterface $input, OutputInterface $output)
4141
{
4242
$generator = new EntityRepositoryGenerator();
43-
$kernel = $this->application->getKernel();
44-
$bundleDirs = $kernel->getBundleDirs();
45-
foreach ($kernel->getBundles() as $bundle) {
46-
$tmp = dirname(str_replace('\\', '/', get_class($bundle)));
47-
$namespace = str_replace('/', '\\', dirname($tmp));
48-
$class = basename($tmp);
49-
50-
if (isset($bundleDirs[$namespace])) {
51-
$destination = realpath($bundleDirs[$namespace].'/..');
52-
if ($metadatas = $this->getBundleMetadatas($bundle)) {
53-
$output->writeln(sprintf('Generating entity repositories for "<info>%s</info>"', $class));
54-
foreach ($metadatas as $metadata) {
55-
if ($metadata->customRepositoryClassName) {
56-
$output->writeln(sprintf(' > generating <comment>%s</comment>', $metadata->customRepositoryClassName));
57-
$generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destination);
58-
}
43+
foreach ($this->application->getKernel()->getBundles() as $bundle) {
44+
$destination = $bundle->getPath();
45+
if ($metadatas = $this->getBundleMetadatas($bundle)) {
46+
$output->writeln(sprintf('Generating entity repositories for "<info>%s</info>"', get_class($bundle)));
47+
foreach ($metadatas as $metadata) {
48+
if ($metadata->customRepositoryClassName) {
49+
$output->writeln(sprintf(' > generating <comment>%s</comment>', $metadata->customRepositoryClassName));
50+
$generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destination);
5951
}
6052
}
6153
}

src/Symfony/Bundle/DoctrineBundle/Command/ImportMappingDoctrineCommand.php

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,33 +42,20 @@ protected function configure()
4242
->setHelp(<<<EOT
4343
The <info>doctrine:mapping:import</info> command imports mapping information from an existing database:
4444
45-
<info>./app/console doctrine:mapping:import "Bundle\MyCustomBundle" xml</info>
45+
<info>./app/console doctrine:mapping:import "MyCustomBundle" xml</info>
4646
4747
You can also optionally specify which entity manager to import from with the <info>--em</info> option:
4848
49-
<info>./app/console doctrine:mapping:import "Bundle\MyCustomBundle" xml --em=default</info>
49+
<info>./app/console doctrine:mapping:import "MyCustomBundle" xml --em=default</info>
5050
EOT
5151
);
5252
}
5353

5454
protected function execute(InputInterface $input, OutputInterface $output)
5555
{
56-
$bundleClass = null;
57-
$bundleDirs = $this->container->get('kernel')->getBundleDirs();
58-
foreach ($this->container->get('kernel')->getBundles() as $bundle) {
59-
if (strpos(get_class($bundle), $input->getArgument('bundle')) !== false) {
60-
$tmp = dirname(str_replace('\\', '/', get_class($bundle)));
61-
$namespace = str_replace('/', '\\', dirname($tmp));
62-
$class = basename($tmp);
63-
64-
if (isset($bundleDirs[$namespace])) {
65-
$destPath = realpath($bundleDirs[$namespace]).'/'.$class;
66-
$bundleClass = $class;
67-
break;
68-
}
69-
}
70-
}
56+
$bundle = $this->application->getKernel()->getBundle($input->getArgument('bundle'));
7157

58+
$destPath = $bundle->getPath();
7259
$type = $input->getArgument('mapping-type') ? $input->getArgument('mapping-type') : 'xml';
7360
if ('annotation' === $type) {
7461
$destPath .= '/Entity';
@@ -100,7 +87,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
10087
$output->writeln(sprintf('Importing mapping information from "<info>%s</info>" entity manager', $emName));
10188
foreach ($metadata as $class) {
10289
$className = $class->name;
103-
$class->name = $namespace.'\\'.$bundleClass.'\\Entity\\'.$className;
90+
$class->name = $bundle->getNamespace().'\\Entity\\'.$className;
10491
if ('annotation' === $type) {
10592
$path = $destPath.'/'.$className.'.php';
10693
} else {

src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
6767
$paths = is_array($dirOrFile) ? $dirOrFile : array($dirOrFile);
6868
} else {
6969
$paths = array();
70-
$bundleDirs = $this->container->get('kernel')->getBundleDirs();
71-
foreach ($this->container->get('kernel')->getBundles() as $bundle) {
72-
$tmp = dirname(str_replace('\\', '/', get_class($bundle)));
73-
$namespace = str_replace('/', '\\', dirname($tmp));
74-
$class = basename($tmp);
75-
76-
if (isset($bundleDirs[$namespace]) && is_dir($dir = $bundleDirs[$namespace].'/'.$class.'/DataFixtures/ORM')) {
77-
$paths[] = $dir;
78-
}
70+
foreach ($this->application->getKernel()->getBundles() as $bundle) {
71+
$paths[] = $bundle->getPath().'/DataFixtures/ORM';
7972
}
8073
}
8174

src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,20 @@ public function registerExtensions(ContainerBuilder $container)
3232
$container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION);
3333
$container->addCompilerPass(new CreateProxyDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING);
3434
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function getNamespace()
40+
{
41+
return __NAMESPACE__;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function getPath()
48+
{
49+
return strtr(__DIR__, '\\', '/');
50+
}
3551
}

0 commit comments

Comments
 (0)