Soy is not a recursive acronym - PHP task runner focused on clean syntax and allowing flexible implementation
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
bin
src/Soy
.gitignore
LICENSE
README.md
composer.json

README.md

Soy

Latest Stable Version Total Downloads Latest Unstable Version License

Introduction

Soy is a PHP task runner focused on clean syntax and allowing flexible implementation.

For more information, see the Why Soy? section.

Tasks

Usage

Include soy in your project with composer:

$ composer require soy-php/soy

Include the tasks you need using composer, Soy doesn't come with any default tasks. For this example we can include the Gulp Task:

$ composer require soy-php/gulp-task

Then create a recipe.php in your project's directory and put your tasks in there. This is the simplest example:

<?php

$recipe = new \Soy\Recipe();

$recipe->component('default', function (\Soy\Gulp\RunTask $gulpTask) {
    $gulpTask->run();
});

return $recipe;

This is a more advanced example using custom CLI arguments and such:

<?php

$recipe = new \Soy\Recipe();

$recipe->cli(function (\League\CLImate\CLImate $climate) {
    $climate->arguments->add('verbose', [
        'prefix' => 'v',
        'longPrefix' => 'verbose',
        'description' => 'Verbose output',
        'noValue' => true,
    ]);
});

$recipe->prepare(\Soy\Gulp\RunTask::class, function (\Soy\Gulp\RunTask $gulpTask) {
    return $gulpTask->setBinary('/usr/local/bin/gulp');
});

$recipe->component('gulp', function (\Soy\Gulp\RunTask $gulpTask, \League\CLImate\CLImate $climate) {
    $verbose = $climate->arguments->defined('verbose');
    if ($verbose) {
        $climate->green('Running gulp');
    }

    $gulpTask
        ->setVerbose($verbose)
        ->run();
});

$recipe->component('default', null, ['gulp']);

return $recipe;

API

The core of Soy is the Recipe, it has two main methods: prepare and component.

Prepare

Prepare can be used to setup defaults for any of the tasks, the first parameter is the class name and the second parameter is a closure which accepts an instance of the class as a first parameter. Make sure you always return the object.

$recipe->prepare(\Soy\Task\GulpTask::class, function (\Soy\Task\GulpTask $gulpTask) {
    return $gulpTask->setBinary('/usr/local/bin/gulp');
});

You can have as many prepare methods as you want and even for the same class. You can also pass a third parameter to prepend the preparation instead of appending it.

Component

Component can be used to execute your tasks, the first parameter is the name of the component, the second parameter is a closure (or null) to execute and the third parameter is an array of dependencies on other components.

$recipe->component('gulp', function (\Soy\Task\GulpTask $gulpTask, \League\CLImate\CLImate $climate) {
    $verbose = $climate->arguments->defined('verbose');
    if ($verbose) {
        $climate->green('Running gulp');
    }

    $gulpTask
        ->setVerbose($verbose)
        ->run();
});

You can put anything in the signature of the closure, the corresponding objects will be injected based on the type-hint.

CLI

There are three ways of defining CLI commands, each suitable for different situations.

If you want to introduce a global argument:

$recipe->cli(function (\League\CLImate\CLImate $climate) {
    $climate->arguments->add([
        'foo' => [
            'description' => 'foo',
            'longPrefix' => 'foo',
            'noValue' => true,
        ],
    ]);
});

If you want to add arguments from a specific task to your component:

$fooComponent = $recipe->component('foo', function (\Soy\Task\FooTask $fooTask, \Soy\Task\BarTask $barTask) {
    $fooTask->run();
    $barTask->run();
});

$fooComponent->cli([\Soy\Task\FooTask::class, 'prepareCli']);
$fooComponent->cli([\Soy\Task\BarTask::class, 'prepareCli']);

You can also use fluent interfacing:

$recipe->component('foo', function (\Soy\Task\FooTask $fooTask, \Soy\Task\BarTask $barTask) {
    $fooTask->run();
    $barTask->run();
})
    ->cli([\Soy\Task\FooTask::class, 'prepareCli'])
    ->cli([\Soy\Task\BarTask::class, 'prepareCli'])
;

If you want to add your own component specific arguments:

$fooComponent = $recipe->component('foo', function (\Soy\Task\FooTask $fooTask) {
    $fooTask->run();
});

$fooComponent->cli(function (\League\CLImate\CLImate $climate) {
    $climate->arguments->add([
        'foo' => [
            'description' => 'foo',
            'longPrefix' => 'foo',
            'noValue' => true,
        ],
    ]);
});

Why Soy?

Soy's focus is to give power back to the developer.

PHP

Soy's recipes are written in plain PHP, no new language you have to familiarize yourself with, nor are we forcing you to use a markup language. The result of this decision is that you are no longer limited in what you can do.

Tasks

Soy's tasks are mostly CLI wrappers, every task has at least one run() method that doesn't accept any arguments. That means tasks are focused on doing one thing and all options are passed through setters. Because the tasks are CLI wrappers, there's no risk of strange integration bugs and debugging becomes easy.

Reusability

Reusability is something we all strive for when developing code, Soy has two ways to reinforce that mindset.

The first pillar to support this is the concept of preparing a task. Task preparations are stacked and can be either appended or prepended. These preparations will be run during the bootstrap phase of Soy, allowing you to manipulate any task. An interesting object you can prepare is CLImate, preparing CLImate allows you to add required/optional arguments/flags, giving you full control over how you interact with your task runner.

The second pillar in supporting reusability is picking setters overs run() method arguments, every task accepts its options through setters allowing you to set defaults in your task preparations. You could create a recipe with sane defaults, require it in your project's recipe and customize only a few things like file paths and such.

Output

Soy doesn't like to talk, it leaves the talking to you. You can enable verbose mode on CLI tasks to get some more insights on the command line used and the output of the command, but there's no default output when a component gets executed. You can add CLImate as an argument in your component and use its awesome output functions to your own likings.