
Conference Session
Drush 14 is Slimmed and Ready for Drupal Core

Moshe Weitzman
Senior Architect & Project Lead
Almost every Drupal developer reaches for Drush within the first hour of a new project, but it has always been a separate download. Drush 14 rebuilds its commands directly on Symfony Console, which cuts the code the team has to maintain and makes each command reusable in other command line tools, including one Drupal core could ship itself. Moshe Weitzman has maintained Drush since the start of Drupal, and here he makes the case for folding these commands into core so a fresh install has the command line it needs out of the box.
Session Description
Drush has been part of the Drupal developer's daily routine for two decades, but it has always lived outside core as a separate install. Drush 14 is a step toward changing that.
Moshe Weitzman (Senior Architect & Project Lead) has maintained Drush since the earliest days of Drupal. He walks through the slimming work in Drush 14: commands now extend the Symfony Console command class directly, with one class per command and the familiar configure, execute, and interact methods, dropping the annotated command layer the project carried for years. Older command files still work, so upgrading a site does not break existing commands, but the new style is the recommended path forward.
He then makes the case for adoption into Drupal core. Because the commands are now plain Symfony Console commands, Drupal core could build its own command line application, pull in the commands it wants, and give every new install a working command line without a separate download. He points to the DEX proposal for how that application could work, and takes questions on why it belongs in core, how the two applications would coexist, and when contrib modules should migrate.
What You Will Learn
- What changed in Drush 14: one class per command, Symfony Console as the base, and the retired annotated command layer
- Why older command files still work and how the deprecations nudge you toward the new style
- How the configure, execute, and interact methods map to authoring a command
- How the output formatter system returns the same data as a table, YAML, JSON, or CSV
- Why Moshe wants Drush commands adopted into Drupal core, and how a core command line application would pull them in
- What the DEX proposal is and what the next steps toward adoption look like
Transcript
[00:00:02] Drush 14, slimmed and ready for Drupal core. If you're looking for a low-code landing page builder, you're not in the right session. My name is Moshe Weitzman. I'm a longtime Drupal developer, since the very beginning in fact, 25 years. I work for Tag1 Consulting and also do independent consulting, so feel free to contact me if you have any needs.
[00:00:40] I want to start off with the status of this project, the slimming project. The status is: Drush is ready. Drush 14 is in pretty good shape, and most of the commands have been slimmed, and you'll hear about what that means. That's what the green check mark is all about. The adoption into Drupal core is in fingers-crossed status. We've talked a little among core maintainers and product managers, but really that's at step one. So we all need to talk about this a bit more, about why it's needed and how best to do it, and see if we can make some progress.
[00:01:28] A little about Drush 14 for people who are curious about adoption. We made a strong effort at backward compatibility with prior releases. Namely, all the commands people have written over the last ten years will continue to work. If you used PHP attributes, which has been the recommended way to author your command for the last few years, that continues to work. Or even the style before that, where you used services YAML files and annotations, those continue to work. So when you upgrade your site to Drush 14, all your old command files still work.
[00:02:11] We did mark a lot of things as deprecated. The Drush Commands class, which all commands have been extending, has been marked as deprecated. So you'll see some crossouts when you look at your code, telling you that you're extending a deprecated class, and that is the nudge to go ahead and learn the new way to do it. The documentation is up to date. If you go to www.drush.org and click on 14.x for your branch, you'll see the new way to author Drush commands, and you'll see it in this presentation as well.
[00:02:50] At this point we're thinking this is going to be Drush 14 and not Drush 13.8, because there were a few things we couldn't keep backward compatibility on, and we didn't want to surprise people. So we have a new major release, but in fact not much will break. Maybe we'll make progress on those edge cases and actually do it as a 13 release, just to encourage adoption of the new style.
[00:03:24] So here are the actual changes in text, and then I'll show you in code. For a long time now, Drush has encouraged you to have one command class and several command definitions as methods inside that one class. The PHP community did not come along with us on that great idea of having lots of methods in one class. So we finally abandoned that and went to one class, one method.
[00:04:00] The class that you extend is now the Symfony Command class directly. It's not Drush Commands. The methods you implement are available to you now, namely configure, execute, and interact. Those are going to be familiar methods for people who've used Symfony Console directly. You use configure to set up the arguments, options, usage examples, and help text for your command.
[00:04:39] When you're trying to hook into some other command, or provide a service for other commands, you use the native events that Symfony Console provides, instead of the hook system that annotated command provided. There are a few things that are unchanged, namely we're still using PHP attributes to modify behavior, and still using the output formatter system, which is a great idea that Drush invented that other people haven't actually adopted, but I think we're right there.
[00:05:21] It's really nice to be able to have your data come out as a table when you're looking at your list of installed modules, but you can also get it as YAML, as JSON, and in any other format you want, CSV, without the command author having to think about providing those things. You can say what fields you want inside your table or CSV, and so forth. All that is provided by the output formatter system.
[00:05:55] So why are we making these changes? For the Drush team, there's a lot less to maintain when we get rid of annotated command and rebase ourselves on top of Console directly. Less maintenance is great, not just because there's less work to do, but the longevity of the project really depends on keeping it small and keeping the maintenance burden low. If you look at the history of Drush for the last ten years, it's been getting smaller and smaller, and this is in that vein. That's why we've remained a really long-standing, well-maintained open source project.
[00:06:40] Symfony Console itself has really improved, and in a lot of ways has started providing the things that annotated command was doing. So this is a good time to go back to Console in a really native way. Console commands are what lots of PHP developers are used to; it's what AI knows how to build really well. So it makes sense from that perspective to rebase on top of Console. I should say further on top of Console: Drush commands are already Symfony Console commands, but now they're just a little more native, without that annotated command layer on top.
[00:07:28] I said Console commands can be reused in other CLIs. This is one of the key integration points with Drupal core: we're providing a soup of Symfony Console commands. When Drupal core wants to create its own console application, it can add ten of our commands and six of its own and have a nice console application its users can use. If you want to use Drush commands that aren't part of that application, you can call vendor/bin/drush and get those commands. But when you call vendor/bin/drupal, which is the console application Drupal core will hopefully provide, you'll get just the commands they want to put in that CLI application.
[00:08:24] I hope that makes sense to people. At least that's the thinking behind how, progressively, this can get adopted by Drupal core: take the commands you want, don't take the other ones. There is a Drupal core proposal by a developer called DPI, a brilliant developer, about how to do the Drupal CLI application in Drupal core. He called it DEX, and it's kind of a strange name; I don't recall right now what it's even short for. It's like Drupal Experience or something. Does someone remember? (From the audience: Extensions.) Extensions. That sounds like an overloaded word there. But okay, it was a really nice thin layer for how to do a CLI in core, so I encourage people to check that out and revive that project in that issue.
[00:09:31] So let's pause the presentation here and go into the code. We're looking at the Drush 14 documentation on www.drush.org. There are four tabs here, so there are four supported ways to author a command right now in Drush 14, like I said, because we wanted to keep all the backward compatibility. Let's take a look at Console, which is the recommended way, and we won't look at the two older ways, which were PHP attributes with annotated, and annotated without attributes. And this Console invocables, which is the Symfony 7.4 way; you can't really do it yet with Drupal, but Drush 14 supports it. It's pretty neat; take a look at that tab when you get a chance.
[00:10:34] So commands start off with this attribute. This comes from Symfony Console itself. You can name, describe, and alias your command; this is also where you say if it's hidden or not. These three attributes are about the output formatter system and the table presentation of your fields. This fourth attribute is also about the output formatter system; it tells the output formatters to expect a rows-of-fields return type, and by default we have a table output formatter.
[00:11:22] Here we get to the class declaration. This is the twig-unused command. The point of this command is that it scans your Twig files and figures out which have been used or not, depending on what's in the PHP storage filesystem directory, and then maybe you can delete unused Twig. That's the idea. It extends the Symfony Console command. It uses the AutowireTrait and the FormatterTrait. Here we have the constructor. You can see that FormatterManager is a Drush service that's getting injected. Here's a Drupal service, Twig Environment. LoggerInterface is a Drush service.
[00:12:08] So you can autowire services from either the Drush container or the Drupal container. What's actually happening under the hood is that Drush is making a compound container with both containers in it, so you can pull services out of either one. Auto-wiring is a great improvement that Drupal put in the last few releases, and that's part of the commands here. I mentioned the Symfony Console configure method earlier; this is an example of a small configure method. Here we're setting the long-style help for the command, we're setting up an argument, here it's the search-paths argument of where we should look for possibly unused Twig files in the filesystem, and here's a usage example.
[00:13:09] The execute method: this is where you do all your work in Symfony Console commands. More or less, in annotated commands you would do all your work in the one method, so this is where all that code goes, into execute. If you choose to have a command that uses the output formatter system, the convention I've been using is that execute only has these three lines, which are completely boilerplate, and you do the actual work in doExecute. And doExecute's job is to build up our rows of fields, which is to say all of the enabled modules as rows, and then return that to execute, which will go ahead and write formatted output.
[00:14:05] So that's it. It's not too hard to convert commands to this Console-style command. You have to change how the arguments and options are declared, mainly move some of your code into an execute method, rename it from the plural local commands to a local table command or something like that, but that's mostly what's necessary. Drush will go ahead and discover your command, and it becomes part of the help listing and gets executable and all of that. There are other fancy things you could do with commands, like hook into each other and add options, and for that you'll want to look into the Drush listener system, which really isn't its own system; it's just implementing the console events that it provides.
[00:15:06] All right, let's go back to the presentation. So this is a little more detail on how it could look to have Drush slimmed down and ready for adoption in Drupal core. Again, take a look at the DEX issue and project. Like I said before, the product managers for Drupal core can go ahead and add the pieces they want to add. Here are some commands that make sense to me as part of an initial grouping that could go into the application, like recipe commands, and existing commands will just go into that same application. You'll call it with vendor/bin/drupal, not with vendor/bin/drush.
[00:16:04] Things that stay in Drush you might have to call with vendor/bin/drush, and unfortunately for a while there may be two different applications you have to use. But this is the way you incrementally adopt things and get it done. So next steps: it's really about discussing if this is a good idea, how to do it, showing support, and doing all of the tests and all of the political work to make it easily digested by Drupal core. Okay, we have like three minutes for questions and comments. One in the back, right here.
[00:17:01] (Question from the audience.) Hello, Dan Friedman, Consensus Enterprises, from Toronto, Canada. It would be good to have it in core, sure, but why does it matter? It works great the way it's been, outside. Why in core?
[00:17:22] So I think the main answer is that we're always trying to improve the adoption of Drupal, and right now when you download Drupal or Drupal CMS, you don't have a piece that you're going to need within an hour. That's not a great experience. You're going to have to read the blog post and figure out where and how you do command line, and does it have a command line, and why do I have to install a third-party thing, and is that really the right way to do it? So in my mind you just eliminate that barrier to entry, and that's reason enough. I think there are other reasons, but I think that's reason enough.
[00:18:11] (Question from the audience.) Anyone else? One up front here. You mentioned that some commands would be in core and in Drush, and others are only in Drush. What does that mean technically? Do they live in a different place in code, or do they have a different attribute on them, or what does it mean?
[00:18:25] I think we would try to avoid that, so it's either-or. The commands, in my vision, will live in the vendor directory under the Drush project. When Drupal is building its CLI application, it says add this command class to my application, add this command, and this command. So when it's building its application class, it's pulling things from the Drush vendor directory. That's the least-coupled way to do it. (From the audience: So it would depend on Drush.) Yes, the Drupal core application would depend on Drush as a dependency, and then add commands to the application like that. There are other ways to do it, to formally bring the commands into the Drupal codebase, and I'd be happy to go with that direction too. I just think there's a lot more code to review and discuss to do it that way. So I think we can discuss how to do it.
[00:19:38] (Question from the audience.) So if Drush becomes a dependency of Drupal core, why would you have the vendor/bin/drupal CLI, because Drush would be available already? Well, you'd have Drush available and you'd have the Drupal CLI available. If things are in the Drupal CLI, that's how it would be documented; it would say go run site-install out of the Drupal CLI application. (From the audience: Is that a temporary situation until something?) Until the 2030s? Yeah.
[00:20:30] (Question from the audience.) There are a lot of contrib modules still using one of the three older ways of writing commands. Is there a proposed way to start migrating them forward to Drush 14, or is it maybe too soon, or already halfway there? Yeah, I think it's just a couple of weeks too soon. We're not quite ready to say exactly when and how the contrib modules should come along, assuming they want to still work with older versions of Drush.
[00:21:08] (Question from the audience.) When you're pulling values out of the input object, does PHPStan help you know the names of the types? Before, you could have typed parameters on your method, and now you just have this one input object that contains the input values, so something needs to help you know what the available values are. I think it's possibly fixed, or it is fixed, in the invocables way of authoring commands. So wait until Symfony 7.4, which will come in the next version of Drupal, and you can write commands that will have that. (From the audience: So you have __invoke and then you have the free signature you can put.) Yes, you have the options and the arguments available there.
[00:22:35] (From the audience: We've got like three minutes for the next presenter.) Okay, so let's wrap it up. Okay.
Event Details
- Conference
- DrupalCon Europe
- Date
- October 15, 2025
- Location
- Vienna, Austria
- Skill Level
- Intermediate