Drush in the Composer Era
1Photo © 2012 by Charlie Nguyen, CC BY 2.0
2
Overview
Using Composer Dependency Hell Site-Local Drush
Configuration Filter The FutureDrush Extensions
It is now possible to use Composer to
add code directly to Drupal:
drush dl drupal-8
cd drupal-8
composer require drupal/og:8.*
composer update will not update
drupal/core; use drush pm-update
drupal for this.
3
Standard Composer used in D8
web
├── autoload.php
├── composer.json
├── core
│ └─ composer.json
├── index.php
└── vendor
└─ autoload.php
4
wikimedia/composer-merge-plugin
{
"name": "drupal/drupal",
"require": {
"composer/installers": "^1.0.21",
"wikimedia/composer-merge-plugin": "^1.3.0"
},
"extra": {
"_readme": [
"By default Drupal loads the autoloader from ./vendor/autoload.php.",
"To change the autoloader you can edit ./autoload.php."
],
"merge-plugin": {
"include": [
"core/composer.json"
],
"recurse": false,
"replace": false,
"merge-extra": false
}
},
}
Uses “split core” to manage Drupal
core files:
composer create-project drupal-
composer/drupal-project:8.x-dev
my-site-name --stability dev --
no-interaction
5
Drupal 8: “drupal-project”
project
├── composer.json
├── vendor
│ └── autoload.php
└── web
├── autoload.php
├── core
│ └── composer.json
└── index.php
6
drupal-project Layout
{
"name": "drupal-composer/drupal-project",
"repositories": [
{
"type": "composer",
"url": "https://packagist.drupal-composer.org"
}
],
"require": {
"composer/installers": "^1.0.20",
"drupal/core": "8.0.*",
"drush/drush": "dev-master",
"drupal/console": "dev-master"
},
"extra": {
"installer-paths": {
"web/core": ["type:drupal-core"],
"web/modules/contrib/{$name}": ["type:drupal-module"],
"drush/contrib/{$name}": ["type:drupal-drush"]
}
}
}
7
Drupal 7: Example CI Scripts
example-drupal7-circle-composer
example-drupal7-travis-composer
CI
+
+
Use craychee/rootcanal to post-
process your Composer install:
"config": {
"bin-dir": "bin"
},
"scripts": {
"post-install-cmd": [
"bin/rootcanal"
],
"post-update-cmd": [
"bin/rootcanal"
]
}
}
http://craychee.io/blog/2015/08/01/no-excuses-part3-composer/ 8
Drupal 7: rootcanal
Dependency Hell
9
The Multiple Autoloaders Problem
10
Two autoloaders may directly or indirectly include the same dependent library.
Drupal 8
require autoload.php
Badly-Behaved Drush Extension
require autoload.php
"name": "symfony/event-dispatcher",
"version": "v2.7.5",
"name": "symfony/event-dispatcher",
"version": "3.*",
Version Mismatch Breaks
11
Event
isPropagationStopped
stopPropagation
symfony/event-dispatcher v2.7.5 symfony/event-dispatcher v3.x
public function dispatch($event_name, Event $event) {
$event->setDispatcher($this);
Event
isPropagationStopped
stopPropagation
setDispatcher
getDispatcher
getName
setName
DrupalComponentEventDispatcherContainerAwareEventDispatcher.dispatch
Site-Local Drush
12
Solution to Dependency Hell
Well-Behaved Drush Extensions
Site-Local Drush
13
Drush dispatch now happens in four phases:
14
Drush Dispatch
Drush Finder Drush Wrapper Drush Launcher
Drush
Application
15
Drush Finder
Responsible for finding the correct Drush to run
● Checks to see if it can find a Drupal site
● If the Drupal site contains a Drush script, use it
● If no Site-Local Drush is found, use global Drush
Does not look at alias files.
PHP Script
Optional. Located at the Drupal Root if it exists.
User may customize this script to:
● Add site-specific options (e.g. commandfile
locations)
● Select the location of the Drush launcher (if in a
non-standard location)
If there is no Drush Wrapper, then the Drush Finder
will find and execute the Drush Launcher.
16
Drush Wrapper
Shell Script
17
Drush Launcher
Sets up the PHP operating environment.
● Select php.ini file to use
● Select php executable to use
● Passes info about environment to Drush
Always launches the Drush that is located next to it.
Shell Script
18
Drush Application
Contains all the code that is Drush.
● Load configuration and command file
● Parse site alias files
● Might dispatch again, e.g. if site alias is remote
PHP
Application
Manage Drush and Drupal
Place Drush and Drupal in the same composer.json file:
cd /path/to/drupal-8-root
composer require drush/drush:8.*
It is also possible to use site-local Drush with a non-Composer-managed
Drupal site just to attach a specific version of Drush to that site:
cd /path/to/old/drupal-root
composer require drush/drush:6.*
19
CRITICAL
Drush Extensions
20Photo © 2011 by Shawn Clover, CC BY-NA 2.0
PROBLEM:
A Drush extension desires to use Composer
Libraries, but also wants to work with both
Composer-managed sites, and sites that do
not use Composer.
SOLUTION:
Include a composer.json file as usual, and
require libraries there. In your drush extension’
s hook_drush_init, call drush_autoload
(__FILE__).
21
Composer Libraries in Extensions
my.drush.inc:
function my_drush_init() {
drush_autoload(__FILE__);
}
IT’S SIMPLE.
If Drush has located a Drupal site that has an autoloader, then it assumes that
all extensions that need an autoloader are part of the Drupal site’s composer.
json file. Drush only loads Drupal’s autoload.php.
If the Drupal site does not have an autoloader, then Drush will find the
extension’s autoloader, and load it.
AN EXTENSION CAN’T GET THIS RIGHT. ALWAYS USE
drush_autoload.
22
drush_autoload Function
Global Extensions
23
Drush Extensions that use Composer libraries must be included locally as part
of your Drupal project:
cd /path/to/project
composer require drupal/drush_iq
Policy files can continue to live in a global location, e.g. $HOME/.drush.
24
Include Global Extensions in Project
{
"name": "drupal/drupal",
"require": {
"composer/installers": "^1.0.21",
"wikimedia/composer-merge-plugin": "^1.3.0",
"drush/drush": "dev-master"
},
"extra": {
"merge-plugin": {
"include": [
"/Users/ME/.drush-extensions/composer.json"
],
"recurse": false,
"replace": false,
"merge-extra": false
}
},
}
Drupal Project
composer.json:
Global Extensions List
composer.json:
$HOME
└── .drush-extensions
├── composer.json
├── composer.lock
├── drush
│ └── registry_rebuild
│ ├── registry_rebuild.drush.inc
│ └── registry_rebuild.php
└── vendor
└── autoload.php
Drush Configuration Filter
25
Configuration Workflow
26
Code and Content move in only one direction, but Configuration can move in
either direction.
To keep development modules out of exported configuration, define:
__ROOT__/drush/drushrc.php:
$command_specific['config-export']['skip-modules'] = array('devel');
$command_specific['config-import']['skip-modules'] = array(‘devel');
This only works with the Drush config-import and config-export
commands. Use caution with the Configuration Synchronization admin page.
27
Configuration Filter
The Future
28
Drush and Drupal Console
29
The more code you have, the more code you
have to maintain.
However, the maintenance process adds value.
New code should benefit from existing code.
Duplicate functions that do the same thing should
be avoided to reduce maintenance requirements.
&
Drush Bootstrap Refactor
30
Drush now has separate bootstrap
classes for each framework
Boot
validRoot($path);
DrupalBoot6
validRoot($path);
DrupalBoot7
validRoot($path);
DrupalBoot8
validRoot($path);
SOMEDAY?
By factoring Site Aliases and Backend Invoke into separate libraries, the “Drush
Finder” can handle remote dispatches.
31
Refactor into Composer Libraries
Drush Finder Drush Wrapper Drush Launcher Drush Application
cd /path/to/drupal-project
composer require pantheon-systems/behat-drush-endpoint
32
Drush Driver Enhancement
YES!

Drush in the Composer Era

  • 1.
    Drush in theComposer Era 1Photo © 2012 by Charlie Nguyen, CC BY 2.0
  • 2.
    2 Overview Using Composer DependencyHell Site-Local Drush Configuration Filter The FutureDrush Extensions
  • 3.
    It is nowpossible to use Composer to add code directly to Drupal: drush dl drupal-8 cd drupal-8 composer require drupal/og:8.* composer update will not update drupal/core; use drush pm-update drupal for this. 3 Standard Composer used in D8
  • 4.
    web ├── autoload.php ├── composer.json ├──core │ └─ composer.json ├── index.php └── vendor └─ autoload.php 4 wikimedia/composer-merge-plugin { "name": "drupal/drupal", "require": { "composer/installers": "^1.0.21", "wikimedia/composer-merge-plugin": "^1.3.0" }, "extra": { "_readme": [ "By default Drupal loads the autoloader from ./vendor/autoload.php.", "To change the autoloader you can edit ./autoload.php." ], "merge-plugin": { "include": [ "core/composer.json" ], "recurse": false, "replace": false, "merge-extra": false } }, }
  • 5.
    Uses “split core”to manage Drupal core files: composer create-project drupal- composer/drupal-project:8.x-dev my-site-name --stability dev -- no-interaction 5 Drupal 8: “drupal-project”
  • 6.
    project ├── composer.json ├── vendor │└── autoload.php └── web ├── autoload.php ├── core │ └── composer.json └── index.php 6 drupal-project Layout { "name": "drupal-composer/drupal-project", "repositories": [ { "type": "composer", "url": "https://packagist.drupal-composer.org" } ], "require": { "composer/installers": "^1.0.20", "drupal/core": "8.0.*", "drush/drush": "dev-master", "drupal/console": "dev-master" }, "extra": { "installer-paths": { "web/core": ["type:drupal-core"], "web/modules/contrib/{$name}": ["type:drupal-module"], "drush/contrib/{$name}": ["type:drupal-drush"] } } }
  • 7.
    7 Drupal 7: ExampleCI Scripts example-drupal7-circle-composer example-drupal7-travis-composer CI + +
  • 8.
    Use craychee/rootcanal topost- process your Composer install: "config": { "bin-dir": "bin" }, "scripts": { "post-install-cmd": [ "bin/rootcanal" ], "post-update-cmd": [ "bin/rootcanal" ] } } http://craychee.io/blog/2015/08/01/no-excuses-part3-composer/ 8 Drupal 7: rootcanal
  • 9.
  • 10.
    The Multiple AutoloadersProblem 10 Two autoloaders may directly or indirectly include the same dependent library. Drupal 8 require autoload.php Badly-Behaved Drush Extension require autoload.php "name": "symfony/event-dispatcher", "version": "v2.7.5", "name": "symfony/event-dispatcher", "version": "3.*",
  • 11.
    Version Mismatch Breaks 11 Event isPropagationStopped stopPropagation symfony/event-dispatcherv2.7.5 symfony/event-dispatcher v3.x public function dispatch($event_name, Event $event) { $event->setDispatcher($this); Event isPropagationStopped stopPropagation setDispatcher getDispatcher getName setName DrupalComponentEventDispatcherContainerAwareEventDispatcher.dispatch
  • 12.
    Site-Local Drush 12 Solution toDependency Hell Well-Behaved Drush Extensions
  • 13.
  • 14.
    Drush dispatch nowhappens in four phases: 14 Drush Dispatch Drush Finder Drush Wrapper Drush Launcher Drush Application
  • 15.
    15 Drush Finder Responsible forfinding the correct Drush to run ● Checks to see if it can find a Drupal site ● If the Drupal site contains a Drush script, use it ● If no Site-Local Drush is found, use global Drush Does not look at alias files. PHP Script
  • 16.
    Optional. Located atthe Drupal Root if it exists. User may customize this script to: ● Add site-specific options (e.g. commandfile locations) ● Select the location of the Drush launcher (if in a non-standard location) If there is no Drush Wrapper, then the Drush Finder will find and execute the Drush Launcher. 16 Drush Wrapper Shell Script
  • 17.
    17 Drush Launcher Sets upthe PHP operating environment. ● Select php.ini file to use ● Select php executable to use ● Passes info about environment to Drush Always launches the Drush that is located next to it. Shell Script
  • 18.
    18 Drush Application Contains allthe code that is Drush. ● Load configuration and command file ● Parse site alias files ● Might dispatch again, e.g. if site alias is remote PHP Application
  • 19.
    Manage Drush andDrupal Place Drush and Drupal in the same composer.json file: cd /path/to/drupal-8-root composer require drush/drush:8.* It is also possible to use site-local Drush with a non-Composer-managed Drupal site just to attach a specific version of Drush to that site: cd /path/to/old/drupal-root composer require drush/drush:6.* 19 CRITICAL
  • 20.
    Drush Extensions 20Photo ©2011 by Shawn Clover, CC BY-NA 2.0
  • 21.
    PROBLEM: A Drush extensiondesires to use Composer Libraries, but also wants to work with both Composer-managed sites, and sites that do not use Composer. SOLUTION: Include a composer.json file as usual, and require libraries there. In your drush extension’ s hook_drush_init, call drush_autoload (__FILE__). 21 Composer Libraries in Extensions my.drush.inc: function my_drush_init() { drush_autoload(__FILE__); }
  • 22.
    IT’S SIMPLE. If Drushhas located a Drupal site that has an autoloader, then it assumes that all extensions that need an autoloader are part of the Drupal site’s composer. json file. Drush only loads Drupal’s autoload.php. If the Drupal site does not have an autoloader, then Drush will find the extension’s autoloader, and load it. AN EXTENSION CAN’T GET THIS RIGHT. ALWAYS USE drush_autoload. 22 drush_autoload Function
  • 23.
    Global Extensions 23 Drush Extensionsthat use Composer libraries must be included locally as part of your Drupal project: cd /path/to/project composer require drupal/drush_iq Policy files can continue to live in a global location, e.g. $HOME/.drush.
  • 24.
    24 Include Global Extensionsin Project { "name": "drupal/drupal", "require": { "composer/installers": "^1.0.21", "wikimedia/composer-merge-plugin": "^1.3.0", "drush/drush": "dev-master" }, "extra": { "merge-plugin": { "include": [ "/Users/ME/.drush-extensions/composer.json" ], "recurse": false, "replace": false, "merge-extra": false } }, } Drupal Project composer.json: Global Extensions List composer.json: $HOME └── .drush-extensions ├── composer.json ├── composer.lock ├── drush │ └── registry_rebuild │ ├── registry_rebuild.drush.inc │ └── registry_rebuild.php └── vendor └── autoload.php
  • 25.
  • 26.
    Configuration Workflow 26 Code andContent move in only one direction, but Configuration can move in either direction.
  • 27.
    To keep developmentmodules out of exported configuration, define: __ROOT__/drush/drushrc.php: $command_specific['config-export']['skip-modules'] = array('devel'); $command_specific['config-import']['skip-modules'] = array(‘devel'); This only works with the Drush config-import and config-export commands. Use caution with the Configuration Synchronization admin page. 27 Configuration Filter
  • 28.
  • 29.
    Drush and DrupalConsole 29 The more code you have, the more code you have to maintain. However, the maintenance process adds value. New code should benefit from existing code. Duplicate functions that do the same thing should be avoided to reduce maintenance requirements. &
  • 30.
    Drush Bootstrap Refactor 30 Drushnow has separate bootstrap classes for each framework Boot validRoot($path); DrupalBoot6 validRoot($path); DrupalBoot7 validRoot($path); DrupalBoot8 validRoot($path); SOMEDAY?
  • 31.
    By factoring SiteAliases and Backend Invoke into separate libraries, the “Drush Finder” can handle remote dispatches. 31 Refactor into Composer Libraries Drush Finder Drush Wrapper Drush Launcher Drush Application
  • 32.
    cd /path/to/drupal-project composer requirepantheon-systems/behat-drush-endpoint 32 Drush Driver Enhancement YES!