-
Notifications
You must be signed in to change notification settings - Fork 9.3k
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
Allow modules to live outside of app/code directory #1206
Conversation
{ | ||
$modulesDir = $this->filesystem->getDirectoryRead(DirectoryList::MODULES); | ||
foreach ($modulesDir->search('*/*/etc/module.xml') as $filePath) { | ||
yield $modulesDir->readFile($filePath); |
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.
Wow, that's the first usage of generators in Magento 2 :)
I've added an in-depth explanation to the OP. |
@joshdifabio using the composer I've been wondering what the best way to solve this problem is as well and have documented my thoughts here. Feel free to also look at the other code changes that were needed as they may help you out this weekend! I'm curious to know how others about the importance of some features:
|
@otoolec Your research will be immensely helpful, thank you. Regarding the four questions you've raised, I'll respond with my own thoughts soon. Unfortunately, I only had time to update the unit tests at the weekend, so the unit build is now passing but the PR is still WIP. |
@joshdifabio Thanks for tackling this - much appreciated! Having something like this work
would be awesome. It currently feels like we are almost there with Magento but we can't yet take full advantage of composer. @otoolec in reply to your questions
I don't think full flexibility is needed and might even make things more complicated (a code review for example).
I think this would be a good compromise - acknowledging that Magento won't be able to port over all modules in time while allowing extensions to use vendor (possibly with a long term view that Magento core modules will also be shifted there)
I think it is still early for M2 extension development so if this is tackled soon the impact would not be that big. It is still in beta after all.
Using composer to install we should get this for free. I think it is a good idea to support what the rest of the php world is seemingly moving towards. |
Module can reside anywhere
I think Magento should be opinionated, so I don't think a module should be able to reside anywhere. I think there should be one obvious right place for a module to reside. I think that place is vendor. ;) Module can reside in vendor or app/codeFor now, for backwards compatibility, I can totally get behind needing to keep some modules in app/code. But it should be deprecated – new modules should be in vendor. Module doesn’t need to provide new configuration
Module can define own internal folder structure (‘src’, ‘tests’, ‘doc’, ‘config’)I think this is in the same vein as my Zen of Python quote above in that module developers should be encouraged to adhere to a consistent structure. But technically, I don't know that it matters that much as long as the module is fully encapsulated. |
@fooman, @kojiromike: Consider the following workflow which, I would say, is the standard method used to build and test a PHP package:
In the above scenario, the module which we are testing does not reside in If, on the other hand, we allow modules to reside anywhere on disk, implemented in this PR, then the above workflow is possible. See the OP to see how this could work. |
I think this is crucial in order to support the most basic PHP workflow: clone, composer install, test. In this workflow, the code of the module being tested will not reside in
I think that allowing modules to reside in
The less boilerplate the better. Developers hate boilerplate.
Do you have any use cases in mind for this? |
Interesting thread. Here is my current opinion until I change my mind! ;-) The reason for supporting app/code (yes, want to change to app/modules - separate discussion) and vendor/ is to support two use cases. The reason for app/code is "code local to my project"; the reason for vendor is "this is downloaded by Composer". If you want to contribute a bug fix to Magento 2, you should git clone the M2 repo and create a pull request. If you want to create a production site, you SHOULD NOT do it this way. You should use composer to download official released versions of all packages. It makes sense them residing in 'vendor'. The general rule is "don't touch what is in vendor because it is not yours". However, it has been pointed out that if every module is in a separate repo, you can use Composer to use git to get the module and put it in vendor, plus if you make local edits (e.g. a bug fix) you can easily push it back to the repo you got it from. This is convenient for developers, but assumes you create a separate git repo per module. The current hackathon approach of copying from vendor to app/code makes it harder (and slower) for developers due to a copy of the vendor files having been made. So I like the idea of supporting two directories, vendor and app/code. The opinionated position is 'vendor is for composer downloads, app/code is for local code'. The fact that a developer can use vendor in another way is a (very useful) side bonus. Oh, and to be clear, if you check out M2 from the master git repo, all the modules are "local" to that project, which is why they are in app/code. But if you build a real site project, they will all be downloaded under the 'vendor' directory instead. It is important to remember you WILL NOT git clone the master M2 repo when building customer sites. |
@joshdifabio I agree with you, but we're talking about different scopes. As a developer, it would be helpful to be able to do
and work on that module. (I believe
|
@alankent Thanks for the reply. There is a third use case, which concerns the active development of a module which lives in its own repository, has its own unit tests and which should be built and statically analysed separately from any particular project code. Think community modules. Consider the following workflow:
This is the standard, modern way of developing PHP packages. In such cases, the package (in this case a Magento module) which is being developed and tested will not be installed to the This PR makes this workflow possible and also allows modules to live in the
@kojiromike Agreed. |
Gotcha. Side bar 1: This is actually another good reason to move service contracts out of implementation modules. When testing, if you have a mock, you can reduce the package download count as you don't need any implementations (and their transient dependencies), just the service contract packages. Side bar 2: The interesting follow on unresolved question to me is how about integration, functional, etc testing. Where do those cross module tests sit and how to invoke them? But I would rather complete the vendor directory goal first and tackle the next step forwards as a separate objective. |
For "Module can define own internal folder structure (‘src’, ‘tests’, ‘doc’, ‘config’)" I was thinking about making it easier to change the structure of a module while still maintaining backwards compatibility for existing modules. The downside is that each module could come up with its own structure making it harder to maintain modules, but I was thinking of this as being similar to modman where the community came up with a structure different from Magento. For “Module doesn’t need to provide new configuration”. I meant that no changes should be necessary for any existing modules to suddenly be used from the vendor directory vs being copied over. With the proposal in this PR there would be some configuration in composer.json and in bootstrap.php. For "Module can reside in vendor or app/code" I was thinking about installed modules, but I completely agree that part of the benefit to allowing modules to live in vendor is that unit tests be run directly from a module's repository, since it no longer tries to copy files over as if it were being installed into magento when |
@otoolec Thanks for clarifying. Any Magento 2 modules which have already been implemented will have been designed to run from This proposal in no way conflicts with functionality which would search |
I was thinking about that use case because if we have a way to prevent copying existing Magento modules without requiring them to make any changes, then we can stop supporting the Composer plugin instead of having to support it until all the existing extension migrate. That doesn't make it a show stopper, but I was interested in getting feedback on if anyone else cared about that.
For some reason I thought every composer package would contain only a single module with metapackages being used to group modules together. I know that this repository violates that rule by putting all of the modules together and in the |
As far as I understand, any existing Magento 2 modules will require |
Do plug-ins need to be enabled automatically, when required with Composer (eg. with the autoload) wouldn't is be possible to use some kind of ServiceProvider class (Like other frameworks) which would be autoloaded when requested and sets up the module paths (and perhaps other bootstrapping needed). So modules can just be temporarily disabled without changing the composer file, or enable of of multiple modules from 1 package. |
Good question. I certainly think it should be possible to explicitly disable modules, but do we want to enforce an additional step after As someone who expects to run How about agencies with code suites which are common to many clients; if a module is added to such a suite, do we want agency developers to have to go through many client projects, individually enabling any modules which are newly required as a result of a My personal preference is probably quite obvious: bootstrap and enable modules by default, and allow developers to disable them explicitly. But that doesn't mean I'm not open to changing my mind! I can appreciate that there might be reasons to require a manual deployment step after installing new modules. |
Should installing a module via composer cause it to be enabled and setup automatically? This would certainly be convenient, but would there be a potential negative impact since modules alter the database when their setup scripts are run? Not sure how everyone feels about having Right now to get a sample module to show up in a clone of this repository one needs to run the following commands:
It should be possible to create a new command that simplifies some of the above if desired. It could also be possible to automatically enable a module but not run Thoughts? |
I think you're right, @otoolec, that it would be inadvisable to let a module make database changes after just a composer install. That said, it's worth considering the use case for an integrator managing projects with many module components. Must that integrator have a script that expressly enables all the modules one by one? Writing such a script would be challenging because you'd have to figure out a way to map a package's composer name to its (one or more) Magento module names. What I'm saying is that there probably should be a way to enable modules on composer install, but I would not want it to be the default behavior. Secondarily, it would be really nice if there were a convenient way to map a composer package's name to its Magento modules, because I have a feeling that's going to come up again. |
Regarding database upgrades; I agree, they shouldn't be executed automatically, particularly as database upgrades are per-environment and most agencies won't be running Composer on UAT or production. Regarding the default state of modules (enabled or disabled) upon installation, I think I'd prefer them to be enabled by default, and I'll try to explain why. Firstly, the way extension developers work at present is to distribute their modules such that they are automatically enabled when they are pulled in by Composer. Check firegento for some examples (note that all the modules specify
Executing database upgrades with a single terminal command is easy enough; Magento developers are used to doing that and it's easy to integrate into the deployment process. Knowing which modules need to be enabled after running Composer would be less straightforward if there were a number of changes. Developers would most likely just enable everything after each update, or they'd forget to do it. Is there value in that approach? If I add a package requirement to
True, but developers are used to this. Also, what if I updated my module and added some new setup scripts? If the module was already installed in a project and was updated, it'd still break as it'd already be enabled but the database schema would be out of date. Developers will always need to run setup scripts after updating their code. I think we should make things as simple as possible and have a single workflow. |
@joshdifabio corner case situation, though it was mentioned here before: what if you download a package consisting of multiple modules and want just half of them being enabled? Right now we have just one package like that, magento2-community-edition, but I feel this will happen more often once bundle packages will be implemented. Another concern with the module enabled via composer plugin is that sometime you may need to distinguish these two steps. For example if we build Web UI wizard on top of the composer, most likely you would like to distinguish package download and installation, because more steps can be added in between (like, database backup) Personally, I would prefer to do what @otoolec proposed to have a wrapper tool on top of composer which can automate package installation and module enable in one step. |
* @param string $moduleName Fully-qualified module name | ||
* @param string $path Absolute file path to the module | ||
*/ | ||
public static function setModulePath($moduleName, $path) |
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.
Static calls are hard-coded dependencies, they can not be substituted or decorated. So please avoid them. Other problem is that with such approach there is no strict rule when all module paths are available, because you don't know if all modules called setModulePaths, and module dev doesn't know when it should call setModulePath.
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.
@antonkril thanks for reviewing this.
Static calls are hard-coded dependencies, they can not be substituted or decorated. So please avoid them.
I agree with you, and I avoid them wherever possible and appropriate. Sometimes, however, it is necessary to use them. Refer to one of the static methods in Magento 2 for an example.
In this case, the only way to achieve what I've proposed in this PR is with a static method call. Please take a look at the section entitiled What would a module look like with this approach? in the OP to see why.
Furthermore, I consider a module to be a static entity in the same way that a class is a static entity. While a class has a single location on disk, so does a module. Consider that autoloaders are registered in PHP statically (via spl_autoload_register
). Some functionality is static. This PR essentially provides autoloading for modules; the reason we need to implement it ourselves here is that packages/modules aren't first-class citizens in PHP in the same way classes are.
Also note that nothing in the Magento core depends directly on that static method call, so it doesn't affect testability.
Other problem is that with such approach there is no strict rule when all module paths are available, because you don't know if all modules called setModulePaths, ...
I'm not sure I understand the argument. If a module doesn't reside in app/code
and doesn't enable itself via the module resolver then yes, it won't be enabled. Similarly, it won't be enabled if it doesn't implement a module.xml
. I don't see what's strange about that.
and module dev doesn't know when it should call setModulePath.
Please read the module implementation suggestion in the OP.
There are a few classes in the Magento code base which will only work with modules located in Thoughts? |
@joshdifabio I'm all for incremental improvements that get us to where we want to go. I think your pull request is a step in the right direction. Was there anything else you wanted to add to this PR? |
…ectory #1206 Code cleanup
…ectory #1206 - more description in docblocks
Was this tested? It works for registering the module but as it looks now, it will never find module configuration files.
|
I'm also not quite sure why |
I haven't specifically checked the PR, however I have created a bunch of modules installed directly (and only) into vendor since, for example https://github.com/vinai/VinaiKopp_EavOptionSetup. Works okay. |
@danslo There are a number of places in the code base which still need re-factoring in order for this PR to work as intended. I believe it's with @otoolec of Magento but I'm not certain. If it's not being looked at I'm happy to nibble away at some of those issues myself? Edit:
They aren't static because they don't have to be static. Those methods are part of Hope that makes some sense! |
@joshdifabio Fair enough on the static discussion, I just never saw an instance of After seeing this PR merged I (wrongly?) assumed this was working. I can definitely confirm that di.xml configuration (and likely others) aren't loaded when not in |
It should be handled by a preference in
It was definitely a fair assumption to make! There are a number of places in the code base at which it is still assumed that all module code lives in |
I also assumed the PR was complete! |
I'll create a new issue after work this evening with a check-list of the areas which require attention, or you can create it now if you'd prefer. We can use that to keep track of what needs doing. I'm sure someone from Magento will let us know what's already been resolved internally. |
We would love this change, but its not high priority internally due to other commitments. Discipline and cut lists is how we are hitting dates reliably. So we love the work but not likely to happen for 2.0 without this wonderful community contributions continuing. |
I'm sure anyone who does this for a living can relate. Thanks for the update, keep up the good work. |
Story - MQE-121 Re-run failed tests for MTF 1.0
Merge mainline
Partially resolves #1013.
This PR aims to enable Magento modules to reside in the
vendor
directory, or indeed anywhere else on disk, without the use of any Composer plugins and without copying or linking any module files.How does it work?
This PR adds the concept of a module registry to the
Magento\Framework\Module
package. This module registry provides Magento with the locations of any modules which do not reside in the standard modules directory (currentlyapp/code
). This PR does not remove the existing module bootstrapping functionality and aims to be backwards compatible with Magento 2's existing module implementation.Added in the PR is a
ModuleRegistryInterface
which exposes two non-static methods which allow Magento to locate modules. Also provided is a concreteRegistrar
implementation which implements theModuleRegistryInterface
while also implementing a staticregisterModule()
method which third party modules can use to register themselves with Magento. Magento itself does not depend on the staticregisterModule()
method, which will help with automated testing of those Magento classes which depend onModuleRegistry
.Modules will be installed using Composer but without the use of any Composer plugins. No code will be copied from vendor, or wherever else the modules are located, into the Magento application directory.
What would a module look like with this approach?
To use this functionality, a module will need to call the static
Registrar::registerModule()
method in order to notify the Magento application of its presence and location.bootstrap.php
composer.json
When Composer's
vendor/autoload.php
is included,autoload_real.php
first processes the various namespaces, class paths and include paths defined in all included composer packages and registers the autoloader. Next,autoload_real.php
include
s all files defined inautoload.files
. This means that, in the above example,bootstrap.php
will be included by Composer and will have access to any classes which are known to the Composer autoloader.Why is this good?
Copying files around using Composer plugins adds a lot of complexity and I believe Magento already know most of the related difficulties. It also makes the standard process of downloading and testing a package very difficult:
However, this proposal enables the above process to work; even if the module is located in the root while the application code is installed to the vendor directory, the
bootstrap.php
file will be included by Composer and will register the module with Magento. This would mean that we could finally, easily, create modules and build our code on Travis, Scrutinizer, or any of the other brilliant, free code coverage and static analysis tools.This weekend (2015-04-24 – 2015-04-26) I'll aim to finish this PR.