forked from consolidation/robo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Documentation on extending / packaging Robo and Robo-based scripts. (c…
…onsolidation#415) * Documentation on extending / packaging Robo and Robo-based scripts. * Document TaskIO in extending.md rather than getting-started.md. Improving wordsmithing in intro to extending.md. Document 'tasks that use tasks'. * Break up 'Packaging' section; put half of it into Getting Started, and rename the remaining half to 'Framework'.
- Loading branch information
1 parent
14df06a
commit 1794a7c
Showing
4 changed files
with
331 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
# Extending | ||
|
||
Robo tasks can be added to your Robo application by using Composer to suppliment the set of built-in tasks that Robo provides by default. To find existing Robo task extensions, search in Packagist for projects of type [robo-tasks](https://packagist.org/search/?type=robo-tasks). | ||
|
||
The convention used to add new tasks for use in your RoboFiles is to create a wrapper trait named 'loadTasks` that instantiates the implementation class for each task. Each task method in the trait should start with the prefix `task`, and should use **chained method calls** for configuration. Task execution should be triggered by the method `run`. | ||
|
||
To include additional tasks in your RoboFile, you must `use` the appropriate `loadTasks` in your RoboFile. See the section [Including Additional Tasks](#including-additional-tasks) below. To create your own Robo extension that provides tasks for use in RoboFiles, then you must write your own class that implements TaskInterface, and create a `loadTasks` trait for it as described in the section [Creating a Robo Extension](#creating-a-robo-extension). | ||
|
||
## Including Additional Tasks | ||
|
||
Additional tasks may be installed into projects that have included Robo via Composer. For example: | ||
``` | ||
$ cd myproject | ||
$ composer require boedah/robo-drush | ||
``` | ||
If any of the tasks you include require external Composer projects themselves, then you must `composer require` these as well. See the `suggests` section of Robo's composer.json file for a list of some projects you might need to require. | ||
|
||
Once the extension you wish to use has been added to your vendor directory, you may then include it from your RoboFile: | ||
``` php | ||
class RoboFile extends \Robo\Tasks | ||
{ | ||
use \Boedah\Robo\Task\Drush\loadTasks; | ||
|
||
public function test() | ||
{ | ||
// ... | ||
} | ||
} | ||
``` | ||
Once you have done this, all of the tasks defined in the extension you selected will be available for use in your commands. | ||
|
||
Note that at the moment, it is not possible to extend Robo when using the robo.phar. This capability may be added in the future via [embedded composer](https://github.com/dflydev/dflydev-embedded-composer). | ||
|
||
## Creating a Robo Extension | ||
|
||
A Robo tasks extension is created by advertising a Composer package of type `robo-tasks` on [Packagist](https://packagist.org/). For an overview on how this is done, see the article [Creating your very own Composer Package](https://knpuniversity.com/screencast/question-answer-day/create-composer-package). Specific instructions for creating Robo task extensions are provided below. | ||
|
||
### Create your composer.json File | ||
|
||
Your composer.json file should look something like the example below: | ||
``` | ||
{ | ||
"name": "boedah/robo-drush", | ||
"description": "Drush CommandStack for Robo Task Runner", | ||
"type": "robo-tasks", | ||
"autoload": { | ||
"psr-4": { | ||
"Boedah\\Robo\\Task\\Drush\\": "src" | ||
} | ||
}, | ||
"require": { | ||
"php": ">=5.5.0", | ||
"consolidation/robo": "~1" | ||
} | ||
} | ||
``` | ||
Customize the name and autoload paths as necessary, and add any additional required projects needed by the tasks that your extensions will provide. The type of your project should always be `robo-tasks`. Robo only supports php >= 5.5.0; you may require a higher version of php if necessary. | ||
|
||
### Create the loadTasks.php Trait | ||
|
||
It is recommended to place your trait-loading task in a `loadTasks` file in the same namespace as the task implementation. | ||
``` | ||
namespace Boedah\Robo\Task\Drush; | ||
use Robo\Container\SimpleServiceProvider; | ||
trait loadTasks | ||
{ | ||
/** | ||
* @param string $pathToDrush | ||
* @return DrushStack | ||
*/ | ||
protected function taskDrushStack($pathToDrush = 'drush') | ||
{ | ||
return $this->task(__FUNCTION__, $pathToDrush); | ||
} | ||
} | ||
``` | ||
Note that the name of the service for a given task must start with the word "task", and must have the same name as the function used to call the task. `$this->task()` looks up the service by name; using the PHP built-in constant __FUNCTION__ for this parameter ensures that the names of these items remain in alignment. | ||
|
||
### Task implementation | ||
|
||
The implementation of each task class should extend \Robo\Task\BaseTask, or some class that extends the same, and should used chained initializer methods and defer all operations that alter the state of the system until its `run()` method. If you follow these patterns, then your task extensions will be usable via Robo collection builders, as explained in the [collections](collections.md) documentation. | ||
|
||
There are many examples of task implementations in the Robo\Task namespace. A very basic task example is provided below. The namespace is `MyAssetTasks`, and the example task is `CompileAssets`. To customize to your purposes, choose an appropriate namespace, and then define as many tasks as you need. | ||
|
||
``` php | ||
<?php | ||
namespace MyAssetTasks; | ||
|
||
trait loadTasks | ||
{ | ||
/** | ||
* Example task to compile assets | ||
* | ||
* @param string $pathToCompileAssets | ||
* @return \MyAssetTasks\CompileAssets | ||
*/ | ||
protected function taskCompileAssets($path = null) | ||
{ | ||
// Always construct your tasks with the `task()` task builder. | ||
return $this->task(CompileAssets::class, $path); | ||
} | ||
} | ||
|
||
class CompileAssets implements \Robo\Contract\TaskInterface | ||
{ | ||
// configuration params | ||
protected $path; | ||
protected $to; | ||
function __construct($path) | ||
{ | ||
$this->path = $path; | ||
} | ||
|
||
function to($filename) | ||
{ | ||
$this->to = $filename; | ||
// must return $this | ||
return $this; | ||
} | ||
|
||
// must implement Run | ||
function run() | ||
{ | ||
//.... | ||
} | ||
} | ||
?> | ||
``` | ||
|
||
To use the tasks you define in a RoboFile, use its `loadTasks` trait as explained in the section [Including Additional Tasks](#including-additional-tasks), above. | ||
|
||
### TaskIO | ||
|
||
To allow tasks access IO, use the `Robo\Common\TaskIO` trait, or inherit your task class from `Robo\Task\BaseTask` (recommended). | ||
|
||
Inside tasks you should print process details with `printTaskInfo`, `printTaskSuccess`, and `printTaskError`. | ||
``` | ||
$this->printTaskInfo('Processing...'); | ||
``` | ||
The Task IO methods send all output through a PSR-3 logger. Tasks should use task IO exclusively; methods such as 'say' and 'ask' should reside in the command method. This allows tasks to be usable in any context that has a PSR-3 logger, including background or server processes where it is not possible to directly query the user. | ||
|
||
### Tasks That Use Tasks | ||
|
||
If one task implementation needs to use other tasks while it is running, it should do so via a `CollectionBuilder` object, as explained in the [Collections](collections.md) documentation. | ||
|
||
To obtain access to a `CollectionBuilder`, a task should implement `BuilderAwareInterface` and use `BuilderAwareTrait`. It will then have access to a collection builder via the `$this->collectionBuilder()` method. | ||
|
||
### Testing Extensions | ||
|
||
If you wish to use the `task()` methods from your `loadTasks` trait in your unit tests, it is necessary to also use the Robo `TaskAccessor` trait, and define a `collectionBuilder()` method to provide a builder. Collection builders are used to initialize all Robo tasks. The easiest way to get a usable collection builder in your tests is to initialize Robo's default dependency injection container, and use it to request a new builder. | ||
|
||
An example of how to do this in a PHPUnit test is shown below. | ||
``` | ||
use League\Container\ContainerAwareInterface; | ||
use League\Container\ContainerAwareTrait; | ||
use Symfony\Component\Console\Output\NullOutput; | ||
use Robo\TaskAccessor; | ||
use Robo\Robo; | ||
class DrushStackTest extends \PHPUnit_Framework_TestCase implements ContainerAwareInterface | ||
{ | ||
use \Boedah\Robo\Task\Drush\loadTasks; | ||
use TaskAccessor; | ||
use ContainerAwareTrait; | ||
// Set up the Robo container so that we can create tasks in our tests. | ||
function setup() | ||
{ | ||
$container = Robo::createDefaultContainer(null, new NullOutput(), [ \Boedah\Robo\Task\Drush\loadTasks::getDrushServices() ]); | ||
$this->setContainer($container); | ||
} | ||
// Scaffold the collection builder | ||
public function collectionBuilder() | ||
{ | ||
$emptyRobofile = new \Robo\Tasks; | ||
return $this->getContainer()->get('collectionBuilder', [$emptyRobofile]); | ||
} | ||
public function testYesIsAssumed() | ||
{ | ||
$command = $this->taskDrushStack() | ||
->drush('command') | ||
->getCommand(); | ||
$this->assertEquals('drush command -y', $command); | ||
} | ||
} | ||
``` | ||
To assert that the output of a command contains some value, use a `Symfony\Component\Console\Output\BufferedOutput` in place of null output when calling Robo::createDefaultContainer(). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# Robo as a Framework | ||
|
||
There are multiple ways to use and package Robo scripts; a few of the alternatives are presented below. | ||
|
||
## Creating a Standalone Phar with Robo | ||
|
||
It is possible to create a standalone phar that is implemented with Robo; doing this does not require the RoboFile to be located in the current working directory, or any particular location within your project. To achieve this, first set up your project as shown in the section [Implementing Composer Scripts with Robo](getting-started.md#implementing-composer-scripts-with-robo). Use of the "scripts" section is optional. | ||
|
||
Next, add an "autoload" section to your composer.json to provide a namespace for your Robo commands: | ||
``` | ||
{ | ||
"name": "myorg/myproject", | ||
"require": { | ||
"consolidation/Robo": "~1" | ||
}, | ||
"autoload":{ | ||
"psr-4":{ | ||
"MyProject\\":"src" | ||
} | ||
} | ||
} | ||
``` | ||
Create a new file for your Robo commands, e.g. `class RoboCommands` in `namespace MyProject\Commands;` in the file `src\Commands\RoboCommands.php`. Optionally, add more task libraries as described in the [extending](extending.md) document. | ||
|
||
Create a startup script similar to the one below, and add it to the root of your project, or some other location of your choosing: | ||
|
||
``` php | ||
#!/usr/bin/env php | ||
<?php | ||
|
||
/** | ||
* If we're running from phar load the phar autoload file. | ||
*/ | ||
$pharPath = \Phar::running(true); | ||
if ($pharPath) { | ||
require_once "$pharPath/vendor/autoload.php"; | ||
} else { | ||
if (file_exists(__DIR__.'/vendor/autoload.php')) { | ||
require_once __DIR__.'/vendor/autoload.php'; | ||
} elseif (file_exists(__DIR__.'/../../autoload.php')) { | ||
require_once __DIR__ . '/../../autoload.php'; | ||
} | ||
} | ||
|
||
$commandClasses = [ \MyProject\Commands\RoboCommands::class ]; | ||
$runner = new \Robo\Runner($commandClasses); | ||
$statusCode = $runner->execute($_SERVER['argv']); | ||
exit($statusCode); | ||
``` | ||
Use [box-project/box2](https://github.com/box-project/box2) to create a phar for your application. Note that if you use Robo's taskPackPhar to create your phar, then `\Phar::running()` will always return an empty string due to a bug in this phar builder. If you encounter any problems with this, then hardcode the path to your autoload file. See the [robo](https://github.com/consolidation-org/Robo/blob/master/robo) script for details. | ||
|
||
## Using Multiple RoboFiles in a Standalone Application | ||
|
||
It is possible to provide as many command classes as you wish to the Robo `Runner()` constructor. You might wish to separate your Robo command implementations into separate Robo files if you have a lot of commands, or if you wish to group similar commands together in the same source file. If you do this, you can simply add more class references to the `$commandClasses` variable shown above. | ||
``` | ||
$commandClasses = [ | ||
\MyProject\Commands\BuildCommands::class, | ||
\MyProject\Commands\DeployCommands::class | ||
]; | ||
``` | ||
If your application has a large number of command files, or if it supports command extensions, then you might wish to use the Command Discovery class to locate your files. The `CommandFileDiscovery` class will use the Symfony Finder class to search for all filenames matching the provided search pattern. It will return a list of class names using the provided base namespace. | ||
``` php | ||
$discovery = new \Consolidation\AnnotatedCommand\CommandFileDiscovery(); | ||
$discovery->setSearchPattern('*Command.php'); | ||
$commandClasses = $discovery->discover('php/MyProject/Commands', '\MyProject\Commands'); | ||
``` | ||
Pass the resulting `$commandClasses` to the `Runner()` constructor as shown above. See the annotated-commands project for more information about the different options that the discovery command takes. | ||
|
||
## Using Your Own Dependency Injection Container with Robo (Advanced) | ||
|
||
It is also possible to completely replace the Robo application with your own. To do this, set up your project as described in the sections above, but replace the Robo runner with your own main event loop. | ||
|
||
Create the Robo dependency injection container: | ||
``` | ||
use League\Container\Container; | ||
$output = new \Symfony\Component\Console\Output\ConsoleOutput(); | ||
$container = \Robo\Robo::createDefaultContainer($input, $output); | ||
``` | ||
If you are using League\Container (recommended), then you may simply add and share your own classes to the same container. If you are using some other DI container, then you should use [delegate lookup](https://github.com/container-interop/fig-standards/blob/master/proposed/container.md#14-additional-feature-delegate-lookup) to combine them. |
Oops, something went wrong.