Skip to content

Commit 589f673

Browse files
committed
feature #120 Allow generation into sub-namespaces (pink6440, weaverryan)
This PR was merged into the 1.0-dev branch. Discussion ---------- Allow generation into sub-namespaces Three changes: 1) Generation is now allowed into sub-namespaces: e.g. `make:controller` passing it `Admin\MainController` would generate into `src/Controller/Admin/MainController.php`. 2) You can pass absolute class names now too - e.g. `OtherNamespace\Controller\FooController`. Maker reads the Composer autoloader to figure out where this should live - e.g. `lib/Controller/FooController.php`. This fixes #2: Maker follows your autoloading rules to find *where* to generate things. 3) [BC] Break for anyone with custom makes: the `MakerInterface` was updated to be much simpler. Instead of `getParameters()`, `getFiles()` and `writeSuccessMessage()`, you just implement `generate()` and do it all in one method. I think using this library to create custom maker commands is still quite uncommon (we are listed as dependencies of VERY few projects on Packagist), so I think we should make this change versus keeping BC code paths for a long time in the future. Cheers! Commits ------- 5d1b976 Fixing possible non-existent dir in test 38f7887 fixing tests 6b2f56f phpcs dccaea1 Fixing bad method & only generating entity if it exists 9e90cbc Adding support for passing \Absolute\Class\Paths as class names c9617ba Minor tweaks - removing extra "." in text and making twig extension interactive 7f02ed1 Maker error message show the relative paths f8d3e09 Making the maker write changes so they can control message ordering 2da8b65 removing php 7.1 code 27ac258 doc'ing BC break aabbedf Refactoring MakerInterface to be more straightforward 5261d6d Updating templates
2 parents fe4d58e + 5d1b976 commit 589f673

Some content is hidden

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

47 files changed

+871
-450
lines changed

CHANGELOG.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
1.1
22
===
33

4-
* [BC BREAK] The MakerInterface changed: `writeNextStepsMessage`
5-
was renamed to `writeSuccessMessage`. You should now extend
6-
`AbstractMaker` instead of implementing the interface directly,
7-
and use `parent::writeSuccessMessage()` to get the normal success
8-
message after the command.
4+
* [BC BREAK] The MakerInterface changed: `getParameters()`, `getFiles()`
5+
and `writeNextStepsMessage()` were removed and `generate()` was added
6+
in their place. We recommend extending `AbstractMaker` instead of implementing
7+
the interface directly, and use `$this->writeSuccessMessage()` to get
8+
the normal "success" message after the command.

src/Command/MakerCommand.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Bundle\MakerBundle\ConsoleStyle;
1616
use Symfony\Bundle\MakerBundle\DependencyBuilder;
1717
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
18-
use Symfony\Bundle\MakerBundle\ExtraGenerationMakerInterface;
18+
use Symfony\Bundle\MakerBundle\FileManager;
1919
use Symfony\Bundle\MakerBundle\Generator;
2020
use Symfony\Bundle\MakerBundle\InputConfiguration;
2121
use Symfony\Bundle\MakerBundle\MakerInterface;
@@ -33,16 +33,16 @@
3333
final class MakerCommand extends Command
3434
{
3535
private $maker;
36-
private $generator;
36+
private $fileManager;
3737
private $inputConfig;
3838
/** @var ConsoleStyle */
3939
private $io;
4040
private $checkDependencies = true;
4141

42-
public function __construct(MakerInterface $maker, Generator $generator)
42+
public function __construct(MakerInterface $maker, FileManager $fileManager)
4343
{
4444
$this->maker = $maker;
45-
$this->generator = $generator;
45+
$this->fileManager = $fileManager;
4646
$this->inputConfig = new InputConfiguration();
4747

4848
parent::__construct();
@@ -56,6 +56,7 @@ protected function configure()
5656
protected function initialize(InputInterface $input, OutputInterface $output)
5757
{
5858
$this->io = new ConsoleStyle($input, $output);
59+
$this->fileManager->setIo($this->io);
5960

6061
if ($this->checkDependencies) {
6162
$dependencies = new DependencyBuilder();
@@ -86,15 +87,14 @@ protected function interact(InputInterface $input, OutputInterface $output)
8687

8788
protected function execute(InputInterface $input, OutputInterface $output)
8889
{
89-
$this->generator->setIO($this->io);
90-
$params = $this->maker->getParameters($input);
91-
$this->generator->generate($params, $this->maker->getFiles($params));
90+
$generator = new Generator($this->fileManager, 'App\\');
9291

93-
if ($this->maker instanceof ExtraGenerationMakerInterface) {
94-
$this->maker->afterGenerate($this->io, $params);
95-
}
92+
$this->maker->generate($input, $this->io, $generator);
9693

97-
$this->maker->writeSuccessMessage($params, $this->io);
94+
// sanity check for custom makers
95+
if ($generator->hasPendingOperations()) {
96+
throw new \LogicException('Make sure to call the writeChanges() method on the generator.');
97+
}
9898
}
9999

100100
public function setApplication(Application $application = null)

src/DependencyInjection/CompilerPass/MakeCommandRegistrationPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function process(ContainerBuilder $container)
3737
MakerCommand::class
3838
)->setArguments([
3939
new Reference($id),
40-
new Reference('maker.generator'),
40+
new Reference('maker.file_manager'),
4141
])->addTag('console.command', ['command' => $class::getCommandName()]);
4242
}
4343
}

src/ExtraGenerationMakerInterface.php

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/FileManager.php

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\MakerBundle;
1313

14+
use Composer\Autoload\ClassLoader;
1415
use Symfony\Component\Console\Style\SymfonyStyle;
1516
use Symfony\Component\Filesystem\Filesystem;
1617

@@ -20,17 +21,19 @@
2021
*
2122
* @internal
2223
*/
23-
final class FileManager
24+
class FileManager
2425
{
2526
private $fs;
2627
private $rootDirectory;
2728
/** @var SymfonyStyle */
2829
private $io;
2930

31+
private static $classLoader;
32+
3033
public function __construct(Filesystem $fs, string $rootDirectory)
3134
{
3235
$this->fs = $fs;
33-
$this->rootDirectory = $rootDirectory;
36+
$this->rootDirectory = rtrim($this->realpath($rootDirectory).'/');
3437
}
3538

3639
public function setIO(SymfonyStyle $io)
@@ -60,11 +63,81 @@ public function fileExists($path): bool
6063

6164
public function relativizePath($absolutePath): string
6265
{
63-
$relativePath = str_replace($this->rootDirectory, '.', $absolutePath);
66+
// see if the path is even in the root
67+
if (false === strpos($absolutePath, $this->rootDirectory)) {
68+
return $absolutePath;
69+
}
70+
71+
$absolutePath = $this->realPath($absolutePath);
72+
73+
$relativePath = ltrim(str_replace($this->rootDirectory, '', $absolutePath), '/');
74+
if (0 === strpos($relativePath, './')) {
75+
$relativePath = substr($relativePath, 2);
76+
}
6477

6578
return is_dir($absolutePath) ? rtrim($relativePath, '/').'/' : $relativePath;
6679
}
6780

81+
public function getPathForFutureClass(string $className)
82+
{
83+
// lookup is obviously modeled off of Composer's autoload logic
84+
foreach ($this->getClassLoader()->getPrefixesPsr4() as $prefix => $paths) {
85+
if (0 === strpos($className, $prefix)) {
86+
$path = $paths[0].'/'.str_replace('\\', '/', str_replace($prefix, '', $className)).'.php';
87+
88+
return $this->relativizePath($path);
89+
}
90+
}
91+
92+
foreach ($this->getClassLoader()->getPrefixes() as $prefix => $paths) {
93+
if (0 === strpos($className, $prefix)) {
94+
$path = $paths[0].'/'.str_replace('\\', '/', $className).'.php';
95+
96+
return $this->relativizePath($path);
97+
}
98+
}
99+
100+
if ($this->getClassLoader()->getFallbackDirsPsr4()) {
101+
$path = $this->getClassLoader()->getFallbackDirsPsr4()[0].'/'.str_replace('\\', '/', $className).'.php';
102+
103+
return $this->relativizePath($path);
104+
}
105+
106+
if ($this->getClassLoader()->getFallbackDirs()) {
107+
$path = $this->getClassLoader()->getFallbackDirs()[0].'/'.str_replace('\\', '/', $className).'.php';
108+
109+
return $this->relativizePath($path);
110+
}
111+
112+
return null;
113+
}
114+
115+
public function getNamespacePrefixForClass(string $className): string
116+
{
117+
foreach ($this->getClassLoader()->getPrefixesPsr4() as $prefix => $paths) {
118+
if (0 === strpos($className, $prefix)) {
119+
return $prefix;
120+
}
121+
}
122+
123+
return '';
124+
}
125+
126+
private function getClassLoader(): ClassLoader
127+
{
128+
if (null === self::$classLoader) {
129+
$autoloadPath = $this->absolutizePath('vendor/autoload.php');
130+
131+
if (!file_exists($autoloadPath)) {
132+
throw new \Exception(sprintf('Could not find the autoload file: "%s"', $autoloadPath));
133+
}
134+
135+
self::$classLoader = require $autoloadPath;
136+
}
137+
138+
return self::$classLoader;
139+
}
140+
68141
private function absolutizePath($path): string
69142
{
70143
if (0 === strpos($path, '/')) {
@@ -73,4 +146,42 @@ private function absolutizePath($path): string
73146

74147
return sprintf('%s/%s', $this->rootDirectory, $path);
75148
}
149+
150+
/**
151+
* Resolve '../' in paths (like real_path), but for non-existent files.
152+
*
153+
* @param string $absolutePath
154+
*
155+
* @return string
156+
*/
157+
private function realPath($absolutePath): string
158+
{
159+
$finalParts = [];
160+
$currentIndex = -1;
161+
162+
foreach (explode('/', $absolutePath) as $pathPart) {
163+
if ('..' === $pathPart) {
164+
// we need to remove the previous entry
165+
if (-1 === $currentIndex) {
166+
throw new \Exception(sprintf('Problem making path relative - is the path "%s" absolute?', $absolutePath));
167+
}
168+
169+
unset($finalParts[$currentIndex]);
170+
--$currentIndex;
171+
172+
continue;
173+
}
174+
175+
$finalParts[] = $pathPart;
176+
++$currentIndex;
177+
}
178+
179+
$finalPath = implode('/', $finalParts);
180+
// Normalize: // => /
181+
// Normalize: /./ => /
182+
$finalPath = str_replace('//', '/', $finalPath);
183+
$finalPath = str_replace('/./', '/', $finalPath);
184+
185+
return $finalPath;
186+
}
76187
}

0 commit comments

Comments
 (0)