Command dependencies revisited

As I already described in Managing command dependencies, we have added two hooks that allow developers to explicitly state what type of requirements need to be fulfilled before loading their custom command.

This works just fine when you know about these hooks and make proper use of them.

But there’s a large number of third-party commands out there already that are not yet making use of these new hooks, obviously. As we want to provide a safe update path, we had to look into providing a mechanism for the commands that might break due to the bootstrapping and package distribution changes.

Automatic dependency resolution to the rescue

As it turns out, having these hooks available makes it very easy to solve the dependency resolution in a fully automated way.

If a sub-command depends on a parent command that is already registered, or if a command does not have a dependency at all, we add the command immediately, just as before. This is the standard behavior.

If a sub-command depends on a parent command that is not yet registered, adding the sub-command immediately would just break. Imagine wanting to add a scaffold my-plugin sub-command, when the scaffold command has not yet been registered. In this case, we hook up the sub-command addition to the after_add_command:<parent command> hook, and add the command to the list of deferred command additions.

Once we add a deferred command when its hook has been triggered, we remove that command from the list of deferred command additions.

If we processed all commands, any remaining additions on the list of deferred command additions are executed as a final step. This is needed for sub-commands like network meta, where the parent does not actually exist (and thus, the after_add_command:<parent command> hook is never called).

Backward compatibility and ease of use

With the above mechanism, all current commands that relied on the fact that bundled commands were always loaded first will still work, even if the bundled commands might now be loaded later, due to the fact that all bundled commands have been split out into their own packages.

What’s more, any command depending on any other command will now just work, no matter what the loading order is, as long as both commands are loaded eventually.