Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration of root namespace #173

Merged
merged 18 commits into from
May 17, 2018

Conversation

upyx
Copy link
Contributor

@upyx upyx commented May 4, 2018

This PR adds configuration for namespaces used in make:* commands that was discussed in #133.
I've used this doc in work and examples from FrameworkBundle.

New parameter:
maker.root_namespace - application root namespace, default: App

To change it, you should create configuration file in config/packages/dev with name maker.yaml or maker.xml or maker.php with root node maker. For example YAML file:

# config/packages/dev/maker.yaml
maker:
    root_namespace: 'App'

I've added some tests. I think we don't need Flex recipe but just document new feature.
I need help for write documentation because my English.

TODO:

  • Tests
  • Documentations
  • Squash commits

@LeJeanbono
Copy link
Contributor

LeJeanbono commented May 4, 2018

Doc and Recipe sound good !
I'll take a look
Nice job ;)

@LeJeanbono
Copy link
Contributor

You did a conf for entity namespace, should we have for others classes (Form, Repo, Twig, etc)?

@upyx
Copy link
Contributor Author

upyx commented May 4, 2018

@LeJeanbono Yes, I think we should. This PR is WIP.

@weaverryan
Copy link
Member

Hey @upyx!

Woo! Thanks for starting this! I have some thoughts:

  1. I think we should only make the root_namespace configurable, at least for now. This bundle is highly opinionated on purpose, and isn't intended to work for all edge cases. Besides, if you really do want to, for example, generate a form in a different directory, you can do that by passing the \Full\Namespace. So, only include root_namespace. It will be much faster to finish & merge this PR anyways.

  2. I don't think we should update the recipe to create the config file, because 99% of people won't need it. Let's just document it. Actually, (maybe in a future PR), it would be cool to detect when the user is having problems using the App\ namespace (e.g. they try to generate some entity called Foo, and we determine that there is no App\ in their autoloader), ask them if they want to define a different root namespace, then create the config file for them. After all, MakerBundle is all about generating code :)

Cheers!

@upyx
Copy link
Contributor Author

upyx commented May 6, 2018

Hi @weaverryan !
At the first look I thought to change only root namespace. But in our project we have many legacy code and have non standard directories. So configuration of many namespaces will be very helpful for our project and may be other. But I understand that our project only one of all. If you are absolutely sure this feature isn't practical then I remove configuration of entity namespace. But think again. We can do bundle more configurable without any disadvantages. Why not do it?

public function getAlias()
{
return 'maker';
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this is needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm not sure... I will be grateful if you explain it. I see doctrine and many others do the same. As I understand, result of this method won't cached. So avoiding it (as you say earlier) has minor negative performance impact.

public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also should not be needed

$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);

$container->setParameter('maker.root_namespace', $config['root_namespace']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we should not create a parameter if we can avoid it - it has minor negative performance impact, and there's just not advantage.

But, making this happen IS a bit technical, so I'll explain, but let me know if you need help. The process is:

  1. First, we need to create an "abstract" service in services.xml that basically registers this service:
    $container->register(
    sprintf('maker.auto_command.%s', Str::asTwigVariable($class::getCommandName())),
    MakerCommand::class
    )->setArguments([
    new Reference($id),
    new Reference('maker.file_manager'),
    ])->addTag('console.command', ['command' => $class::getCommandName()]);
    (this is basically a "template" service).

2 In the compiler pass, we would then use ChildDefinition to effectively "duplicate" that service and make any changes we need. Here's an example from core: https://github.com/symfony/symfony/blob/cada38f520784cc3f248874ad268e76fc99ba8d9/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L563

  1. In this class, instead of creating a parameter, we would simply set the 3rd argument to the abstract service (created in step 1) to this value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use abstract service is great idea! I'll try it.

However where I can get root_namespace in DI context (compiler pass or service.xml) if it is not stored as "parameter"?

The second, if we are deciding to configure root namespace only, it can be more practical - to remove config and use just a parameter. What do think?

P.S.
My English leaves much to be desired. Please, fill free to correct me and ask to explain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, great question. And, you've done a REALLY good job so far making this all work with ChildDefinition :).

For this last part, in this file, you should fetch the maker.auto_command.abstract definition and HERE set its first argument to $config['root_namespace']. You're doing this in the compiler pass right now. Just move that one part of the code here, and then you won't need to rely on the parameter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and for DoctrineHelper, instead of referencing the %maker.root_namespace% parameter in services.xml, you will also just leave that blank in services.xml, and set the argument manually in this class.

@JSkalskiSBG
Copy link

Other ways to fix it for 'advanced users with other namespace then App' would be:

  1. check namespace of Kernel.php
  2. handle namespace parameter:
bin/console maker:command my-command --namespace Custom\\Namespace\\Project
  1. handle namespace given in name:
bin/console maker:command \\Custom\\Namespace\\Project\\my-command
-- OR:
bin/console maker:command \\Custom\\Namespace\\Project\\Command\\my-command

Right now 3rd option gives error:

[ERROR] The "\Custom\Namespace\Project\Command\my-command" command name is not valid because it would be implemented by
"\Custom\Namespace\Project\Command\myCommand" class, which is not valid as a PHP class name (it must start with
a letter or underscore, followed by any number of letters, numbers, or underscores).

@weaverryan
Copy link
Member

About that error - that looks like a bug to me - we may have too much validation on make:command. We need to fix that :)

@upyx
Copy link
Contributor Author

upyx commented May 10, 2018

Guys! Let's finish this work.

Could you help me with these questions:

  1. Does implementation of getAlias() and getConfiguration() methods interfere to performance? Should I really delete these method? I have deleted these method.
  2. What do we configure the root namespace only or namespaces for all commands?
  3. If we configure root namespace only can I remove configuration file and use parameters?
  4. If we continue to use the configuration file how parameters can be removed?
  5. Will we use a recipe?
  6. What tests should I add?

@weaverryan what do you think about it?

@JSkalskiSBG
Copy link

About this solution:
It looks bad with current config file:

root_namespace: 'App'
entity_namespace: '%maker.root_namespace%\Entity'

If we got special config for 'entity', why not special config for 'command'?
Someone may want to keep it in MySpace\Console, so adding 'Command' to namespace is bad idea.
Someone may also use other directory than 'src/Command'.

I think that options '--namespace My\Name\Space\CustomDir' and '--dirpath src/CustomDir' would be much better (directory path relational to folder with composer.json).

Issue should be renamed to 'Add configurable namespace and file path'.
Code that fix this issue should fix it in all 'make' commands, not only 'make:entity' and 'make:command'.

About performance:
maker-bundle should be installed only in 'dev' environment.
Does anyone know, if loading config will affect perfomance after installation on production without 'require-dev' from composer?

@upyx
Copy link
Contributor Author

upyx commented May 11, 2018

Hello @LeJeanbono! I've already removed entity_namespace from configuration as @weaverryan advised. But I prefer to add more configurable options. However we still able to add it later. You are right about dev environment so we shouldn't care about <1ms performance impact. I don't like idea about options because it requires to type more letters every time. But really benefit of this bundle it is creation files quick and simple.

Symfony uses aggressive caching of configuration so performance very weak depends on configuration size. However default App\Kernel class has that string:

$loader->load($confDir . '/{packages}/*' . self::CONFIG_EXTS, 'glob');
$loader->load($confDir . '/{packages}/' . $this->environment . '/**/*' . self::CONFIG_EXTS, 'glob');

So if you will place maker.yaml to config/packages directory it would be affected on production environment. But you can place one in config/packages/dev to fix it.

@upyx upyx changed the title [WIP] Add configuration of root and entity namespaces [WIP] Add configuration of root namespaces May 11, 2018
@upyx upyx changed the title [WIP] Add configuration of root namespaces [WIP] Add configuration of root namespace May 11, 2018
@upyx
Copy link
Contributor Author

upyx commented May 11, 2018

I've added tests for changed namespace cases. @weaverryan could you check it, please?

@LeJeanbono
Copy link
Contributor

Done for dev env @upyx

Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @upyx!

Nice work - we're close, I've left some more comments :).

I also have 3 other requests, if you want to try them!

  1. For tests, make sure we have a MakeEntity test, and one that has a relation. I want to make sure that you can (A) generate an entity correctly when your root namespace is changed and (B) the system correctly looks in the custom namespace for the relation (e.g. if I just type that I want to relate to a Comment entity, it will look for that in Custom\Entity\Comment. I also want to make sure that the repository class generates into the Custom\Repository namespace, there is a current bug where it generates into App\Repository,

  2. At the beginning of ALL commands (so, probably in MakerCommand, if we do not detect that the user's app has any autoload rules for their root_namespace, let's add a warning:

It looks like your app may be using a namespace other than "ROOT_NAMESPACE_HERE". see https://link-to-docs for how to configure this.

  1. And finally, we DO need to add a little note in the docs for this bundle about this new option :). I don't think we should have a recipe.

new Reference('maker.file_manager'),
])->addTag('console.command', ['command' => $class::getCommandName()]);
$commandDefinition = new ChildDefinition('maker.auto_command.abstract');
$commandDefinition->setClass(MakerCommand::class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not needed, because you already specified it in services.xml

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed - actually class not inherits to child services. However it may be bug. Documentation says:

All attributes on the parent service are shared with the child except for shared, abstract and tags. These are not inherited from the parent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I debugged it. Really class inherits. It is resolved in ResolveChildDefinitionsPass but AddConsoleCommandPass calls earlier (when class is set to null).

$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);

$container->setParameter('maker.root_namespace', $config['root_namespace']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, great question. And, you've done a REALLY good job so far making this all work with ChildDefinition :).

For this last part, in this file, you should fetch the maker.auto_command.abstract definition and HERE set its first argument to $config['root_namespace']. You're doing this in the compiler pass right now. Just move that one part of the code here, and then you won't need to rely on the parameter.

$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);

$container->setParameter('maker.root_namespace', $config['root_namespace']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and for DoctrineHelper, instead of referencing the %maker.root_namespace% parameter in services.xml, you will also just leave that blank in services.xml, and set the argument manually in this class.


public function __construct(FileManager $fileManager, string $projectDirectory, DoctrineHelper $doctrineHelper)
public function __construct(FileManager $fileManager, DoctrineHelper $doctrineHelper, string $projectDirectory, string $entityNamespace)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid needing to inject the $entityNamespace ALSO here, let's add a getEntityNamespace() method to DoctrineHelper.

'phpunit.xml.dist',
'<env name="KERNEL_CLASS" value="App\\Kernel" />',
'<env name="KERNEL_CLASS" value="'.$rootNamespace.'\\Kernel" />'
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we need to do all of this. I should be enough to create the maker.yaml file with the config AND add one new PSR4 namespace autoload to composer.json. I don't think we need to completely eliminate and convert the rest of the app from the App\ namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I did so. But such solution has issue: tests pass when it mustn't because autoloader. After debugging tests we can remove it. But I think complete converting namespace is better because it is case for we are doing all of this 😄

->changeRootNamespace('Custom')
->addPreMakeCommand('composer dump-autoload')
];

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only really need to test one case, because each maker is using the exact same code to place files. Well, maybe 2 cases: MakeCommand (as a general case) and MakeEntity, because it is indeed a bit more complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. I will remove useless tests

@upyx
Copy link
Contributor Author

upyx commented May 14, 2018

Thank you for review @weaverryan! I've done changes by your requests. Could you review again, please?
And may I ask you to do PR in documentation?

Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! I think this is the last round of changes :)

$this->rootNamespace
)
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of an exception, let's convert this to a warning. Something like:

$this->io->note([
    sprintf('It looks like your app may be using a namespace other than "%s".', $this->rootNamespace),
    'See https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html#configuration to configure this.
]);

The #configuration part of the link doesn't exist yet, but we'll add it.

@@ -163,6 +163,11 @@ public function getNamespacePrefixForClass(string $className): string
return $this->autoloaderUtil->getNamespacePrefixForClass($className);
}

public function isNamespaceConfiguredToAutoload(string $namespace): string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: bool return type

*
* @return bool
*/
public function isNamespaceConfiguredToAutoload(string $namespace)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add : bool return type, and remove the @param and @return - they are duplicated by the code itself.

$command->setName('make:foo');
$tester = new CommandTester($command);
$tester->execute(array());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this test. We'll just need to instead look at the output, instead of the exception when we convert it to just a message.

@weaverryan
Copy link
Member

Actually, I just made all the changes - including the docs - because they were so minor. Let's see if the tests still pass!

* master:
  improve make:form arguments description
  Make the Generator class a service
  Add a functional test
  Check if the class exists
  Add an interactive question
  API Platform support: add an --api-resource flag for the make:entity command
@weaverryan
Copy link
Member

Also updated after a nasty merge conflict with a related PR. Let's see if the tests pass. @upyx can you check out the latest code to make sure the conflict didn't mess anything up? A lot of the same lines of code were changed.

@weaverryan weaverryan changed the title [WIP] Add configuration of root namespace Add configuration of root namespace May 17, 2018
@upyx
Copy link
Contributor Author

upyx commented May 17, 2018

Oh! Great! I see you strongly determined to comlete this 😃

@weaverryan
Copy link
Member

Ha - I am! It’s a missing feature. I knew that :). And I’ll make a release as soon as it’s done.

@weaverryan
Copy link
Member

Thanks for you work @upyx! The test failures are unrelated, they need to be fixed on master.

@weaverryan weaverryan merged commit 42b7008 into symfony:master May 17, 2018
weaverryan added a commit that referenced this pull request May 17, 2018
…averryan)

This PR was merged into the 1.0-dev branch.

Discussion
----------

Add configuration of root namespace

This PR adds configuration for namespaces used in `make:*` commands that was discussed in #133.
I've used [this doc](https://symfony.com/doc/current/bundles/configuration.html) in work and examples from `FrameworkBundle`.

New parameter:
`maker.root_namespace` - application root namespace, default: `App`

To change it, you should create configuration file in `config/packages/dev` with name `maker.yaml` or `maker.xml` or `maker.php` with root node `maker`. For example YAML file:
```yaml
# config/packages/dev/maker.yaml
maker:
    root_namespace: 'App'
```

I've added some tests. I think we don't need Flex recipe but just document new feature.
I need help for write documentation because my English.

TODO:
- [x] Tests
- [x] Documentations
- [ ] Squash commits

Commits
-------

42b7008 Fixing accidental removal of property
545a422 Merge branch 'master' into configurable_namespace
b92f19c Merge branch 'master' into configurable_namespace
bf1b092 updating for now-missing property
b5658c2 Merge branch 'master' into configurable_namespace
7440ade return type stuff
656116c Changing from an exception
5605d6e adding docs
23a715c Merge remote-tracking branch 'upstream/master' into configurable_namespace
152ffa6 The most important fix
f34fa39 Add exception with hint message when root namespace is not found
758a2c9 Refactor AutoloaderUtil
5423250 Remove maker.root_namespace parameter and improve tests
7157e88 Fix file creation on Windows
657f653 Remove useless code
a425953 Add functional tests for custom root namespace cases
d4139d4 Make "abstract" command and remove entity namespace from config
51d7138 Add configuration of root and entity namespaces
@upyx
Copy link
Contributor Author

upyx commented May 18, 2018

I am glad to work with you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants