Skip to content

Commit

Permalink
Add support for providing command/hook information via php8 Attribute…
Browse files Browse the repository at this point in the history
…s. (#4821)

* Support command authoring with PHP Attributes in addition to annotations.

* Revert composer changes and instead prepare php8 tests a bit more.

* Update linter spec

* Improve docs

* Convert a method to use AC and DR namespace aliases.

* Two lines for two use statements per psr2. sigh.

* woops - dont change composer files.

* Use class constant

* Factor Bootstrap levels into a value class for completion in Bootstrap Attribute

* Add Kernel Attribute with ExpectedValues

* Various fixes

* revert composer.* and PHPCS

* No AttributeInterface and use NoArgumentsBase

* Param -> Argument

* Test Union type and #[DefaultFields]

* Move NoArgumentsBase into Drush

* Remove unneeded PHP8 CI alias

* Add docs for xkcd attributes implementation
  • Loading branch information
weitzman committed Sep 30, 2021
1 parent 4927b9a commit b90445c
Show file tree
Hide file tree
Showing 39 changed files with 664 additions and 56 deletions.
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ jobs:
- run: php --version
- run: composer config platform.php 8.0.0
- run: composer update
- run: composer info composer/semver
- run: composer lint
- run: composer unit
- run: composer functional
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"cbf": "phpcbf",
"lint": [
"find includes -name '*.inc' -print0 | xargs -0 -n1 php -l",
"find src -name '*.php' -print0 | xargs -0 -n1 php -l",
"find src -name '*.php' -and ! -path 'src/Attributes/*' -print0 | xargs -0 -n1 php -l",
"find tests -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
Expand Down
5 changes: 3 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ services:
# More info at https://github.com/wodby/php
# We don't want their drupal-php image as that ships with a Drush inside.
drupal:
image: wodby/php:${PHP_TAG-7.2-dev}
image: wodby/php:${PHP_TAG-7.4-dev}
container_name: ${PROJECT_NAME-unish}_drupal
environment:
PHP_SENDMAIL_PATH: /dev/null
UNISH_DB_URL: ${UNISH_DB_URL-mysql://root:password@mariadb}
COLUMNS: ${COLUMNS-80} # Set 80 columns for docker exec -it.
## Read instructions at https://wodby.com/docs/stacks/drupal/local/#debugging-cli-requests
PHP_XDEBUG:
PHP_XDEBUG_DEFAULT_ENABLE:
PHP_XDEBUG_MODE:
PHP_START_WITH_REQUEST:
PHP_IDE_CONFIG:
PHP_XDEBUG_REMOTE_HOST:
volumes:
Expand Down
3 changes: 2 additions & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Creating a new Drush command or porting a legacy command is easy. Follow the ste
1. Drush will then report that it created a commandfile, a drush.services.yml file and a composer.json file. Edit those files as needed.
1. Use the classes for the core Drush commands at [/src/Drupal/Commands](https://github.com/drush-ops/drush/tree/10.x/src/Drupal/Commands) as inspiration and documentation.
1. See the [dependency injection docs](dependency-injection.md) for interfaces you can implement to gain access to Drush config, Drupal site aliases, etc.
1. A commandfile that will only be used on PHP8+ can [use Attributes](https://github.com/drush-ops/drush/pull/4821) instead of Annotations.
1. Write PHPUnit tests based on [Drush Test Traits](https://github.com/drush-ops/drush/blob/10.x/docs/contribute/unish.md#drush-test-traits).
1. Once your two files are ready, run `drush cr` to get your command recognized by the Drupal container.

Expand Down Expand Up @@ -42,7 +43,7 @@ It is also possible to use [version ranges](https://getcomposer.org/doc/articles
In Drush 9, the default services file, `drush.services.yml`, will be used in instances where there is no `services` section in the Drush extras of the project's composer.json file. In Drush 10, however, the services section must exist, and must name the services file to be used. If a future Drush extension is written such that it only works with Drush 10 and later, then its entry would read `"drush.services.yml": "^10"`, and Drush 9 would not load the extension's commands. It is all the same recommended that Drush 9 extensions explicitly declare their services file with an appropriate version constraint.

## Altering Drush Command Info
Drush command info (annotations) can be altered from other modules. This is done by creating and registering 'command info alterers'. Alterers are class services that are able to intercept and manipulate an existing command annotation.
Drush command info (annotations/attributes) can be altered from other modules. This is done by creating and registering 'command info alterers'. Alterers are class services that are able to intercept and manipulate an existing command annotation.

In order to alter an existing command info, follow the steps below:

Expand Down
2 changes: 1 addition & 1 deletion docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Then, the command may ask the provided hook manager to return a list of handlers
}
```

Other command handlers may provide implementations by implementing `@hook on-event my-event`.
Other command handlers may provide implementations by implementing `@hook on-event my-event` or `#[CLI/Hook(type: 'on-event', target: 'my-event')]`.

```php
/**
Expand Down
35 changes: 35 additions & 0 deletions examples/Commands/XkcdCommands.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<?php
namespace Drush\Commands;

use Drush\Attributes\Command;
use Drush\Attributes\Help;
use Drush\Attributes\Option;
use Drush\Attributes\Argument;
use Drush\Attributes\Usage;
use Drush\Exec\ExecTrait;

/**
Expand Down Expand Up @@ -33,6 +38,36 @@ class XkcdCommands extends DrushCommands
* @aliases xkcd
*/
public function fetch($search = null, $options = ['image-viewer' => 'open', 'google-custom-search-api-key' => 'AIzaSyDpE01VDNNT73s6CEeJRdSg5jukoG244ek'])
{
$this->doFetch($search, $options);
}

/**
* This command uses PHP8 Attributes instead of annotations. This
* is recommended for commandfiles that only need to run on PHP8+. All
* Attributes provided by Drush core are listed at
* https://www.drush.org/latest/api/Drush/Attributes.html
*/
#[Command(name: 'xkcd:fetch-attributes', aliases: ['xkcd-attributes'])]
#[Argument(name: 'search', description: 'Optional argument to retrieve the cartoons matching an index number, keyword search or "random". If omitted the latest cartoon will be retrieved.')]
#[Option(name: 'image-viewer', description: 'Command to use to view images (e.g. xv, firefox). Defaults to "display" (from ImageMagick).')]
#[Option(name: 'google-custom-search-api-key', description: 'Google Custom Search API Key, available from https://code.google.com/apis/console/. Default key limited to 100 queries/day globally.')]
#[Help(description: 'Retrieve and display xkcd cartoons (attribute variant).')]
#[Usage(name: 'drush xkcd', description: 'Retrieve and display the latest cartoon')]
#[Usage(name: 'drush xkcd sandwich', description: 'Retrieve and display cartoons about sandwiches.')]
#[Usage(name: 'drush xkcd 123 --image-viewer=eog', description: 'Retrieve and display cartoon #123 in eog.')]
#[Usage(name: 'drush xkcd random --image-viewer=firefox', description: 'Retrieve and display a random cartoon in Firefox.')]
public function fetchAttributes($search = null, $options = ['image-viewer' => 'open', 'google-custom-search-api-key' => 'AIzaSyDpE01VDNNT73s6CEeJRdSg5jukoG244ek'])
{
$this->doFetch($search, $options);
}

/**
* @param $search
* @param array $options
* @throws \Exception
*/
protected function doFetch($search, array $options): void
{
if (empty($search)) {
$this->startBrowser('http://xkcd.com');
Expand Down
53 changes: 10 additions & 43 deletions includes/bootstrap.inc
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ use Drush\Drush;
use Drush\Log\LogLevel;

/**
* No bootstrap.
*
* Commands that only preflight, but do not bootstrap, should use
* a bootstrap level of DRUSH_BOOTSTRAP_NONE.
* @deprecated see DrupalBootLevels::NONE
*/
define('DRUSH_BOOTSTRAP_NONE', 0);

/**
* Use drush_bootstrap_max instead of drush_bootstrap_to_phase
* Use MAX instead of drush_bootstrap_to_phase
*
* This constant is only usable as the value of the 'bootstrap'
* item of a command object, or as the parameter to
* drush_bootstrap_to_phase. It is not a real bootstrap state.
*
* @deprecated see DrupalBootLevels::NONE
*/
define('DRUSH_BOOTSTRAP_MAX', -2);

Expand All @@ -28,61 +27,29 @@ define('DRUSH_BOOTSTRAP_MAX', -2);
*/
define('DRUSH_BOOTSTRAP_DRUSH', 0);


/**
* Set up and test for a valid drupal root, either through the -r/--root options,
* or evaluated based on the current working directory.
*
* Any code that interacts with an entire Drupal installation, and not a specific
* site on the Drupal installation should use this bootstrap phase.
* @deprecated see DrupalBootLevels::ROOT
*/
define('DRUSH_BOOTSTRAP_DRUPAL_ROOT', 1);

/**
* Set up a Drupal site directory and the correct environment variables to
* allow Drupal to find the configuration file.
*
* If no site is specified with the -l / --uri options, Drush will assume the
* site is 'default', which mimics Drupal's behaviour.
*
* If you want to avoid this behaviour, it is recommended that you use the
* DRUSH_BOOTSTRAP_DRUPAL_ROOT bootstrap phase instead.
*
* Any code that needs to modify or interact with a specific Drupal site's
* settings.php file should bootstrap to this phase.
* @deprecated see DrupalBootLevels::SITE
*/
define('DRUSH_BOOTSTRAP_DRUPAL_SITE', 2);

/**
* Load the settings from the Drupal sites directory.
*
* This phase is analagous to the DRUPAL_BOOTSTRAP_CONFIGURATION bootstrap phase in Drupal
* itself, and this is also the first step where Drupal specific code is included.
*
* This phase is commonly used for code that interacts with the Drupal install API,
* as both install.php and update.php start at this phase.
* @deprecated see DrupalBootLevels::CONFIGURATION
*/
define('DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION', 3);

/**
* Connect to the Drupal database using the database credentials loaded
* during the previous bootstrap phase.
*
* This phase is analogous to the DRUPAL_BOOTSTRAP_DATABASE bootstrap phase in
* Drupal.
*
* Any code that needs to interact with the Drupal database API needs to
* be bootstrapped to at least this phase.
* @deprecated see DrupalBootLevels::DATABASE
*/
define('DRUSH_BOOTSTRAP_DRUPAL_DATABASE', 4);

/**
* Fully initialize Drupal.
*
* This is analogous to the DRUPAL_BOOTSTRAP_FULL bootstrap phase in
* Drupal.
*
* Any code that interacts with the general Drupal API should be
* bootstrapped to this phase.
* @deprecated see DrupalBootLevels::FULL
*/
define('DRUSH_BOOTSTRAP_DRUPAL_FULL', 5);

1 change: 1 addition & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
<rule ref="PSR2"/>

<arg name="warning-severity" value="0"/>
<arg value="s" />
</ruleset>
10 changes: 10 additions & 0 deletions src/Attributes/Argument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Argument extends \Consolidation\AnnotatedCommand\Attributes\Argument
{
}
28 changes: 28 additions & 0 deletions src/Attributes/Bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Drush\Attributes;

use Attribute;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Drush\Boot\DrupalBoot;
use Drush\Boot\DrupalBootLevels;
use JetBrains\PhpStorm\ExpectedValues;

#[Attribute(Attribute::TARGET_METHOD)]
class Bootstrap
{
/**
* @param $level
* The level to bootstrap to.
*/
public function __construct(
#[ExpectedValues(valuesFromClass: DrupalBootLevels::class)] public string $level,
) {
}

public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo)
{
$args = $attribute->getArguments();
$commandInfo->addAnnotation('bootstrap', $args['level']);
}
}
10 changes: 10 additions & 0 deletions src/Attributes/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Command extends \Consolidation\AnnotatedCommand\Attributes\Command
{
}
10 changes: 10 additions & 0 deletions src/Attributes/DefaultFields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class DefaultFields extends \Consolidation\AnnotatedCommand\Attributes\DefaultFields
{
}
10 changes: 10 additions & 0 deletions src/Attributes/DefaultTableFields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class DefaultTableFields extends \Consolidation\AnnotatedCommand\Attributes\DefaultTableFields
{
}
10 changes: 10 additions & 0 deletions src/Attributes/FieldLabels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class FieldLabels extends \Consolidation\AnnotatedCommand\Attributes\FieldLabels
{
}
10 changes: 10 additions & 0 deletions src/Attributes/FilterDefaultField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class FilterDefaultField extends \Consolidation\AnnotatedCommand\Attributes\FilterDefaultField
{
}
10 changes: 10 additions & 0 deletions src/Attributes/Help.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Help extends \Consolidation\AnnotatedCommand\Attributes\Help
{
}
10 changes: 10 additions & 0 deletions src/Attributes/Hook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Hook extends \Consolidation\AnnotatedCommand\Attributes\Hook
{
}
29 changes: 29 additions & 0 deletions src/Attributes/Kernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Drush\Attributes;

use Attribute;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Drush\Boot\DrupalBoot;
use Drush\Boot\DrupalBootLevels;
use Drush\Boot\Kernels;
use JetBrains\PhpStorm\ExpectedValues;

#[Attribute(Attribute::TARGET_METHOD)]
class Kernel
{
/**
* @param $kernel
* The kernel name.
*/
public function __construct(
#[ExpectedValues(valuesFromClass: Kernels::class)] public string $kernel,
) {
}

public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo)
{
$args = $attribute->getArguments();
$commandInfo->addAnnotation('kernel', $args['kernel']);
}
}
10 changes: 10 additions & 0 deletions src/Attributes/Misc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Misc extends \Consolidation\AnnotatedCommand\Attributes\Misc
{
}
16 changes: 16 additions & 0 deletions src/Attributes/NoArgumentsBase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Drush\Attributes;

use Consolidation\AnnotatedCommand\Attributes\AttributeInterface;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;

abstract class NoArgumentsBase
{
protected const NAME = 'annotation-name';

public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo)
{
$commandInfo->addAnnotation(static::NAME, null);
}
}
10 changes: 10 additions & 0 deletions src/Attributes/Option.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Drush\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Option extends \Consolidation\AnnotatedCommand\Attributes\Option
{
}

0 comments on commit b90445c

Please sign in to comment.