-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[RAD][HttpKernel] Create a MicroBundle base class #19596
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
Conversation
Can we make route definitions available? |
@ro0NL you mean configuring them ? |
@ro0NL the kernel is the root of the project, a bundle isn't. |
Understand, im talking about registering (mounting) |
This is possible in the micro kernel trait because the kernel is also a service but bundles aren't and i don't think they should be. |
As usual? // AppKernel::loadRoutes
$bundleRoutes = $loader->import('bundle:SomeBundle:getRoutes', 'service'); // getRoutes to clarify
$bundleRoutes->addPrefix('/some-bundle'); I understand this is not as easy (kernel is a service like you mentioned). However for me, having the kernel as a service is just as weird as having a bundle as a service. Hence i want to propose separating this (kernel/bundle in the ecosystem vs. kernel/bundle as a service). |
@ro0NL ok got it, you would like to be able to use the bundle class as a resource. |
@ro0NL Does this bundle solve your use-case https://github.com/rollerworks/RouteAutowiringBundle? Edit. Hmm, you want to define routes as a service completely? It should be possible, but you would have to register the Bundle class as a service which seems like a terrible idea 😉 Or you need to create a complete route service definition, which is not easy I know of experience 💀 The main advantage of MicroBundle I see is the removal is Extension class. |
I guess for now i would workaround using the kernel as a service, which couples perhaps better anyway. // SomeBundle
public function getRoutes() {
return new RouteCollection(/*...*/);
}
// Kernel
public function loadRoutes(LoaderInterface $loader) {
$bundleRoutes = $loader->import('kernel:getSomeBundleRoutes', 'service');
$bundleRoutes->addPrefix('/some-bundle');
}
public function getSomeBundleRoutes() {
return $this->getBundle('Some')->getRoutes();
} Im not even sure it's supported though... importing routes from a service? |
* | ||
* @author Guilhem N. <egetick@gmail.com> | ||
*/ | ||
abstract class MicroBundle extends Bundle implements ExtensionInterface, ConfigurationExtensionInterface, ConfigurationInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this needs to be a ConfigurationInterface
.. we can eventually just call Processor::process
...
Instead of that, implementing PrependExtensionInterface
could be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes indeed i didn't know this method. I'll change that asap, thanks :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After all I don't think we should do that. ConfigurationExtensionInterface
is used in commands such as ConfigDebugCommand
and ConfigDumpReferenceCommand
(should be at least, they won't work if the extension doesn't implement this interface).
And as ConfigurationExtensionInterface
returns an instance of ConfigurationInterface
, the MicroBundle
must be an instance of ConfigurationInterface
.
About PrependExtensionInterface
it will only allow people to not implement the interface and it won't be used most of the time so I don't see any benefits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what about CompilerPassInterface
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hason this interface is already easy to implement (no need of code to make it work on a bundle).
Would such feature be accepted? |
Imo it should be, as it's the missing part of the micro kernel right now. |
I don't really understand why this is useful in the context of a micro-*. The application using the MicroKernelTrait should probably be bundle-less. What's the benefit or introducing a Bundle in such cases? |
@fabpot that's useful for tests for the micro kernel but it is mostly useful for most small bundles that doesn't need a complex structure. |
Sorry, I wasn't clear in my previous comment. I'm not saying that this is not simpler that the default Bundle class; I just think you don't need a bundle at all. |
@fabpot i understand but i don't think micro apps are the biggest target here. |
I think that the extension system should never be used for anything but Open-Source/shared bundles. In which case, there is no need for something simpler. In any case, naming it like MicroKernel as one might think that there are related, but they are not. |
@fabpot why wouldn't we need something simpler? An extension may only contain services loading with maybe a few configuration parameters to manage and in this case it looks overkill to have 3 classes to do that. |
Disagree, i truly consider a micro app built from simple bundles.
Separation of concerns and reuseability.
They are :) In the sense that a kernel can load bundles. However naming it micro could cause users to think it's required to have micro bundles, when using a micro kernel. Which indeed is not the case. Maybe we can do better naming... edit: |
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getNamespace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ener-Getick i think this is not truly compatible with Bundle::getNamespace
, perhaps it works.. but the intention is different. What about not implementing ExtensionInterface
, but rather return a generic/callback extension from MicroBundle::getContainerExtension
. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed you're right... I have to find some time to rework that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, sorry for the delay
8866522
to
a1d3431
Compare
{ | ||
} | ||
|
||
protected function buildContainer(array $config, ContainerBuilder $container) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure but i'd prefer the arguments in reverse order (builder first).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This follows the Extension::load
signature but both are fine for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it should be called loadContainer
then, to clarify a bit more and dont create too much confusion with Bundle::build
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, Bundle::build
builds the bundle (so just after the kernel is created) while Bundle::buildContainer
builds the container (which happens much later in the kernel life).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function build(ContainerBuilder $container)
.. it builds the container ;-) (ref)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ener-Getick maybe just mimic (public) ExtensionInterface::load
as (protected) SimpleBundle::loadExtension
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we consider adding bundle passes to the container as part of the container building, it does build the container of course :P When i talk about container building, I mostly think about services/parameters which should not be added at this level.
The naming is kind of hard at this level, build
is one the first methods executed (it may not be clear enough), about the new method I'd like it to be clear that it is based on configuration.
What about configureBundle
? or configureContainer
?
private $bundle; | ||
private $buildContainer; | ||
|
||
public function __construct(SimpleBundle $bundle, Closure $buildContainer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this class is internal, we could just call $bundle->buildContainer()
with a little reflection magic :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but i don't like much accessing private functions with reflection. Maybe we could use Closure::bind
, i hesitated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API would be cleaner.. :) public function __construct(SimpleBundle $bundle)
and we are truly tight to SimpleBundle
, not bridging it with a closure, ie. you cant abuse it.
return $treeBuilder; | ||
} | ||
|
||
protected function buildConfiguration(NodeParentInterface $rootNode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be ArrayNodeDefinition
which is probably more convenient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would limit the possibilities in case someone only want a scalar definition here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each bundle should still define it's config under an alias namespace.. right? The root node is an array node by default: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php#L34
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed you're right. Maybe we can even use TreeBuilder
i don't think there's an use case to use something else anyway.
final public function getConfigTreeBuilder() | ||
{ | ||
$treeBuilder = new TreeBuilder(); | ||
$rootNode = $treeBuilder->root($this->getAlias()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getAlias
does not exists ;) you should override SimpleExtension::getAlias
and create a unique alias for each simple bundle class.
public function __construct(SimpleBundle $bundle, Closure $buildContainer) | ||
{ | ||
$this->bundle = $bundle; | ||
$this->buildContainer = Closure::bind(function (array $config, ContainerBuilder $container) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is usually called only once during building phase right? ie. do we really need property cache? 2nd. constructor arg is unused now ;-)
} | ||
|
||
/** | ||
* Simple {@link ExtensionInterface} supporting {@link MicroBundle}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{@link SimpleBundle}
24180d1
to
6128e57
Compare
// check naming convention | ||
$basename = preg_replace('/Bundle$/', '', $this->bundle->getName()); | ||
|
||
return Container::underscore($basename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Container
is not imported :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should definitely add tests... But i'd like the symfony members' point of view before spending too much time on them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understand, i already planned taking it (if you're ok with it ;-)) and it's not accepted (hence the review :)). Perhaps create a composer gist then? Anyway.. hope this gets excepted 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, that's made to be reused ;)
I would prefer seing this merged in symfony, i think it loses most of its interest if it's not simple and quick to use.
return $treeBuilder; | ||
} | ||
|
||
protected function buildConfiguration(TreeBuilder $rootNode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$rootNode
is not of type TreeBuilder
;-) I'd prefer ArrayNodeDefinition
(or the interface otherwise) and let getConfigTreeBuilder
root with the right alias.
{ | ||
} | ||
|
||
protected function buildContainer(array $config, ContainerBuilder $container) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really dont like this name 😕 imo. it's confusing with Bundle::build(ContainerBuilder $container)
.
I think it makes more sense to have SimpleBundle::loadExtension
(as we're always called from SimpleExtension::load
).
What about configureBundle ? or configureContainer ?
We really should consider having Bundle::build
already and what is best DX. So.., it's called from Kernel::prepareContainer
, which is called from Kernel::buildContainer
. Ie. it's easy to assume build
already acts as buildContainer
, it's just poor naming imo.
However, in that spirit, we could also go with simply SimpleBundle::load
. Or configure
from your pov.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about injecting a chained loader pointing to Resources/config
?
Then imo it would be more appropriate to name this method load
.
{ | ||
$config = $this->processConfiguration($this->bundle, $configs); | ||
|
||
$f = Closure::bind(function (array $config, ContainerBuilder $container) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be \Closure
.
{ | ||
} | ||
|
||
protected function buildContainer(array $config, ContainerBuilder $container) | ||
protected function load(array $config, ContainerBuilder $container, LoaderInterface $loader) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it :D
@ro0NL thanks for your reviews, I fixed your last comments :) The last version injects a loader in class AppBundle extends SimpleBundle
{
protected function load(array $config, ContainerBuilder $container, LoaderInterface $loader)
{
// Loads a directory
$loader->load('store');
// Loads a single file
$loader->load('basket.yml');
}
} For now the path to |
We could also consider |
@ro0NL in the constructor? I was thinking about a protected getter. Keeping things verbose work as well but it's not very friendly when you have a lot of files to load. Letting the user choose the paths he wants to use is better imo. |
Yeah.. like This path is usually user-defined (as symfony is flexible).. not sure introducing some special new path (i cant think of name either.. |
@GuilhemN given PHPArray file config doesnt come to core, and this is practically a big self-bridging class... im :-+0: i guess. It gave some good ideas for smaller apps though 👍 |
@ro0NL why would we need to update the config in a bundle ? A bundle should only configure services/parameters. |
You dont :) I really like the combination of it all in that sense; for having smaller apps and bundles. Basically the whole philosophy on it's lowest level. Not ending up with a big fat dir. structure and fancy config :) |
Still 👎 on adding this in core. ping @symfony/deciders |
I'm also 👎 The argument a single class is simpler doesn't convince me. Number of classes is not necessarily and indicator of complexity. What worries me with the "SimpleBundle" is that it mixes several responsibilities and can quickly get out of hand. I also think it's better to have one way of doing things. Btw, if you insist on having everything in a single place perhaps anonymous classes could be helpful. |
Thus, this PR still wants to support all features available with an extension, but puts them all in the bundle class. This does not make any sense IMO. If you want to process configuration, define the appropriate classes |
This PR introduces a new class:
MicroBundle
.It is a all-in-one class replacing the usual
Bundle-Extension-Configuration
scheme by a simple interface that is enough for many app bundles:This api is strongly inspired from the
Bundle
class of theKnpRadBundle
.