├── .gitignore ├── LICENSE ├── README.md ├── bin └── soy ├── composer.json └── src └── Soy ├── Cli.php ├── Component.php ├── Exception ├── CliArgumentDuplicationException.php ├── CliTaskException.php ├── Diagnoser.php ├── FatalErrorException.php ├── NoRecipeReturnedException.php ├── RecipeFileNotFoundException.php ├── SoyException.php └── UnknownComponentException.php ├── Recipe.php ├── Soy.php └── Task ├── CliTask.php └── TaskInterface.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Rick Kuipers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Soy 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/soy-php/soy/v/stable)](https://packagist.org/packages/soy-php/soy) [![Total Downloads](https://poser.pugx.org/soy-php/soy/downloads)](https://packagist.org/packages/soy-php/soy) [![Latest Unstable Version](https://poser.pugx.org/soy-php/soy/v/unstable)](https://packagist.org/packages/soy-php/soy) [![License](https://poser.pugx.org/soy-php/soy/license)](https://packagist.org/packages/soy-php/soy) 4 | 5 | ## Introduction 6 | Soy is a PHP task runner focused on clean syntax and allowing flexible implementation. 7 | 8 | For more information, see the [Why Soy?](#why-soy) section. 9 | 10 | ## Tasks 11 | - [Codeception](https://github.com/soy-php/codeception-task) 12 | - [Doctrine Migrations](https://github.com/soy-php/doctrine-migrations-task) 13 | - [Grunt](https://github.com/soy-php/grunt-task) 14 | - [Gulp](https://github.com/soy-php/gulp-task) 15 | - [Phinx](https://github.com/soy-php/phinx-task) 16 | - [PHP Code Sniffer](https://github.com/soy-php/phpcs-task) 17 | - [PHP Lint](https://github.com/soy-php/php-lint-task) 18 | - [PHP Mess Detector](https://github.com/soy-php/phpmd-task) 19 | - [Replace](https://github.com/soy-php/replace-task) 20 | - [Symfony Build Parameters](https://github.com/Enrise/soy-symfony-build-parameters-task) 21 | 22 | ## Usage 23 | Include soy in your project with composer: 24 | 25 | ```sh 26 | $ composer require soy-php/soy 27 | ``` 28 | 29 | Include the tasks you need using composer, Soy doesn't come with any default tasks. 30 | For this example we can include the Gulp Task: 31 | 32 | ```sh 33 | $ composer require soy-php/gulp-task 34 | ``` 35 | 36 | Then create a `recipe.php` in your project's directory and put your tasks in there. 37 | This is the simplest example: 38 | 39 | ```php 40 | component('default', function (\Soy\Gulp\RunTask $gulpTask) { 45 | $gulpTask->run(); 46 | }); 47 | 48 | return $recipe; 49 | ``` 50 | 51 | This is a more advanced example using custom CLI arguments and such: 52 | 53 | ```php 54 | cli(function (\League\CLImate\CLImate $climate) { 59 | $climate->arguments->add('verbose', [ 60 | 'prefix' => 'v', 61 | 'longPrefix' => 'verbose', 62 | 'description' => 'Verbose output', 63 | 'noValue' => true, 64 | ]); 65 | }); 66 | 67 | $recipe->prepare(\Soy\Gulp\RunTask::class, function (\Soy\Gulp\RunTask $gulpTask) { 68 | return $gulpTask->setBinary('/usr/local/bin/gulp'); 69 | }); 70 | 71 | $recipe->component('gulp', function (\Soy\Gulp\RunTask $gulpTask, \League\CLImate\CLImate $climate) { 72 | $verbose = $climate->arguments->defined('verbose'); 73 | if ($verbose) { 74 | $climate->green('Running gulp'); 75 | } 76 | 77 | $gulpTask 78 | ->setVerbose($verbose) 79 | ->run(); 80 | }); 81 | 82 | $recipe->component('default', null, ['gulp']); 83 | 84 | return $recipe; 85 | ``` 86 | 87 | ## API 88 | The core of Soy is the Recipe, it has two main methods: `prepare` and `component`. 89 | 90 | ### Prepare 91 | Prepare can be used to setup defaults for any of the tasks, the first parameter is the class name and the second 92 | parameter is a closure which accepts an instance of the class as a first parameter. 93 | Make sure you always return the object. 94 | 95 | ```php 96 | $recipe->prepare(\Soy\Task\GulpTask::class, function (\Soy\Task\GulpTask $gulpTask) { 97 | return $gulpTask->setBinary('/usr/local/bin/gulp'); 98 | }); 99 | ``` 100 | You can have as many `prepare` methods as you want and even for the same class. You can also pass a third parameter 101 | to prepend the preparation instead of appending it. 102 | 103 | ### Component 104 | Component can be used to execute your tasks, the first parameter is the name of the component, the second 105 | parameter is a closure (or null) to execute and the third parameter is an array of dependencies on other components. 106 | 107 | ```php 108 | $recipe->component('gulp', function (\Soy\Task\GulpTask $gulpTask, \League\CLImate\CLImate $climate) { 109 | $verbose = $climate->arguments->defined('verbose'); 110 | if ($verbose) { 111 | $climate->green('Running gulp'); 112 | } 113 | 114 | $gulpTask 115 | ->setVerbose($verbose) 116 | ->run(); 117 | }); 118 | ``` 119 | 120 | You can put anything in the signature of the closure, the corresponding objects will be injected based on the type-hint. 121 | 122 | ### CLI 123 | There are three ways of defining CLI commands, each suitable for different situations. 124 | 125 | If you want to introduce a global argument: 126 | 127 | ```php 128 | $recipe->cli(function (\League\CLImate\CLImate $climate) { 129 | $climate->arguments->add([ 130 | 'foo' => [ 131 | 'description' => 'foo', 132 | 'longPrefix' => 'foo', 133 | 'noValue' => true, 134 | ], 135 | ]); 136 | }); 137 | ``` 138 | 139 | If you want to add arguments from a specific task to your component: 140 | 141 | ```php 142 | $fooComponent = $recipe->component('foo', function (\Soy\Task\FooTask $fooTask, \Soy\Task\BarTask $barTask) { 143 | $fooTask->run(); 144 | $barTask->run(); 145 | }); 146 | 147 | $fooComponent->cli([\Soy\Task\FooTask::class, 'prepareCli']); 148 | $fooComponent->cli([\Soy\Task\BarTask::class, 'prepareCli']); 149 | ``` 150 | 151 | You can also use fluent interfacing: 152 | 153 | ```php 154 | $recipe->component('foo', function (\Soy\Task\FooTask $fooTask, \Soy\Task\BarTask $barTask) { 155 | $fooTask->run(); 156 | $barTask->run(); 157 | }) 158 | ->cli([\Soy\Task\FooTask::class, 'prepareCli']) 159 | ->cli([\Soy\Task\BarTask::class, 'prepareCli']) 160 | ; 161 | ``` 162 | 163 | If you want to add your own component specific arguments: 164 | 165 | ```php 166 | $fooComponent = $recipe->component('foo', function (\Soy\Task\FooTask $fooTask) { 167 | $fooTask->run(); 168 | }); 169 | 170 | $fooComponent->cli(function (\League\CLImate\CLImate $climate) { 171 | $climate->arguments->add([ 172 | 'foo' => [ 173 | 'description' => 'foo', 174 | 'longPrefix' => 'foo', 175 | 'noValue' => true, 176 | ], 177 | ]); 178 | }); 179 | ``` 180 | 181 | ## Why Soy? 182 | Soy's focus is to give power back to the developer. 183 | 184 | ### PHP 185 | Soy's recipes are written in plain PHP, no new language you have to familiarize yourself with, nor are we forcing 186 | you to use a markup language. The result of this decision is that you are no longer limited in what you can do. 187 | 188 | ### Tasks 189 | Soy's tasks are mostly CLI wrappers, every task has at least one `run()` method that doesn't accept any arguments. 190 | That means tasks are focused on doing one thing and all options are passed through setters. Because the tasks are 191 | CLI wrappers, there's no risk of strange integration bugs and debugging becomes easy. 192 | 193 | ### Reusability 194 | Reusability is something we all strive for when developing code, Soy has two ways to reinforce that mindset. 195 | 196 | The first pillar to support this is the concept of [preparing](#prepare) a task. Task preparations are stacked and 197 | can be either appended or prepended. These preparations will be run during the bootstrap phase of Soy, allowing you 198 | to manipulate any task. An interesting object you can prepare is CLImate, preparing CLImate allows you to add 199 | required/optional arguments/flags, giving you full control over how you interact with your task runner. 200 | 201 | The second pillar in supporting reusability is picking setters overs `run()` method arguments, every task accepts 202 | its options through setters allowing you to set defaults in your task preparations. You could create a recipe with sane 203 | defaults, require it in your project's recipe and customize only a few things like file paths and such. 204 | 205 | ### Output 206 | Soy doesn't like to talk, it leaves the talking to you. You can enable verbose mode on CLI tasks to get some more 207 | insights on the command line used and the output of the command, but there's no default output when a component gets 208 | executed. You can add [CLImate](http://climate.thephpleague.com/) as an argument in your component and use its awesome 209 | output functions to your own likings. 210 | -------------------------------------------------------------------------------- /bin/soy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handle(); 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soy-php/soy", 3 | "description": "PHP task runner focused on clean syntax and allowing flexible implementation", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Rick Kuipers", 8 | "email": "io@rskuipers.com" 9 | } 10 | ], 11 | "require": { 12 | "league/climate": "^3.0", 13 | "php-di/php-di": "^5.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Soy\\": "src/Soy/" 18 | } 19 | }, 20 | "bin": [ 21 | "bin/soy" 22 | ], 23 | "extra": { 24 | "branch-alias": { 25 | "dev-develop": "0.2.x-dev" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Soy/Cli.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'description' => 'The component to run', 23 | 'defaultValue' => 'default', 24 | ], 25 | 'help' => [ 26 | 'description' => 'Show usage', 27 | 'longPrefix' => 'help', 28 | 'noValue' => true, 29 | ], 30 | 'version' => [ 31 | 'description' => 'Show version', 32 | 'longPrefix' => 'version', 33 | 'noValue' => true, 34 | ], 35 | 'noDiagnostics' => [ 36 | 'description' => 'Disable diagnostics', 37 | 'longPrefix' => 'no-diagnostics', 38 | 'noValue' => true, 39 | ], 40 | 'recipe' => [ 41 | 'description' => 'The recipe file to use', 42 | 'longPrefix' => 'recipe', 43 | 'defaultValue' => self::DEFAULT_RECIPE_FILE, 44 | ] 45 | ]; 46 | 47 | public function handle() 48 | { 49 | $soy = $this->bootstrap(); 50 | 51 | $defaultArguments = $this->defaultArguments; 52 | 53 | $soy->getRecipe()->prepare(CLImate::class, function (CLImate $climate) use ($defaultArguments) { 54 | $climate->arguments->add($defaultArguments); 55 | return $climate; 56 | }, true); 57 | 58 | $soy->prepare(); 59 | 60 | $container = $soy->getContainer(); 61 | 62 | /** @var CLImate $climate */ 63 | $climate = $container->get(CLImate::class); 64 | $climate->arguments->parse(); 65 | 66 | $componentName = $this->parseComponentFromCli($climate); 67 | 68 | $soy->prepareCli($climate, $componentName); 69 | $this->validateCli($climate); 70 | 71 | if ($climate->arguments->defined('help')) { 72 | $climate->green(sprintf('Soy version %s by @rskuipers', Soy::VERSION)); 73 | $climate->usage(); 74 | exit; 75 | } 76 | 77 | $soy->execute($componentName); 78 | } 79 | 80 | private function registerErrorHandlers() 81 | { 82 | set_exception_handler(function (Exception $exception) { 83 | Diagnoser::diagnose($exception); 84 | }); 85 | 86 | register_shutdown_function(function () { 87 | $error = error_get_last(); 88 | if (is_array($error)) { 89 | Diagnoser::diagnose(new FatalErrorException( 90 | $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] 91 | )); 92 | } 93 | }); 94 | } 95 | 96 | /** 97 | * @return Soy 98 | * @throws Exception 99 | * @throws NoRecipeReturnedException 100 | * @throws RecipeFileNotFoundException 101 | */ 102 | private function bootstrap() 103 | { 104 | $climate = new CLImate(); 105 | $climate->arguments->add($this->defaultArguments); 106 | $climate->arguments->parse(); 107 | 108 | if ($climate->arguments->defined('version')) { 109 | $climate->out(Soy::VERSION); 110 | exit; 111 | } 112 | 113 | if (!$climate->arguments->defined('noDiagnostics')) { 114 | $this->registerErrorHandlers(); 115 | } 116 | 117 | $recipeFile = $climate->arguments->get('recipe'); 118 | if (!is_file($recipeFile)) { 119 | throw new RecipeFileNotFoundException('Recipe file not found at path ' . $recipeFile, $recipeFile); 120 | } 121 | 122 | chdir(dirname($recipeFile)); 123 | 124 | $recipe = include_once basename($recipeFile); 125 | 126 | if (!$recipe instanceof Recipe) { 127 | throw new NoRecipeReturnedException('No recipe returned in file ' . realpath($recipeFile)); 128 | } 129 | 130 | return new Soy($recipe); 131 | } 132 | 133 | /** 134 | * @param CLImate $climate 135 | */ 136 | private function validateCli(CLImate $climate) 137 | { 138 | $longPrefixes = []; 139 | $prefixes = []; 140 | 141 | $arguments = $climate->arguments->all(); 142 | foreach ($arguments as $argument) { 143 | if (in_array($argument->longPrefix(), $longPrefixes)) { 144 | throw new CliArgumentDuplicationException('Duplicate longPrefix: ' . $argument->longPrefix()); 145 | } 146 | $longPrefixes[] = $argument->longPrefix(); 147 | 148 | if ($argument->prefix() && in_array($argument->prefix(), $prefixes)) { 149 | throw new CliArgumentDuplicationException('Duplicate prefix: ' . $argument->prefix()); 150 | } 151 | $prefixes[] = $argument->prefix(); 152 | } 153 | } 154 | 155 | /** 156 | * @param CLImate $climate 157 | * @return string 158 | */ 159 | private function parseComponentFromCli(CLImate $climate) 160 | { 161 | $componentName = $climate->arguments->get('component'); 162 | 163 | if (strpos($componentName, '-') === 0) { 164 | $componentName = $climate->arguments->all()['component']->defaultValue(); 165 | } 166 | 167 | return $componentName; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Soy/Component.php: -------------------------------------------------------------------------------- 1 | name = $name; 32 | $this->callable = $callable; 33 | } 34 | 35 | /** 36 | * @param callable $callable 37 | * @return $this 38 | */ 39 | public function cli(callable $callable) 40 | { 41 | $this->cliPreparations[] = $callable; 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return callable[] 47 | */ 48 | public function getCliPreparations() 49 | { 50 | return $this->cliPreparations; 51 | } 52 | 53 | /** 54 | * @param Container $container 55 | * @return callable 56 | */ 57 | public function execute(Container $container) 58 | { 59 | $container->call($this->callable); 60 | } 61 | 62 | /** 63 | * @param CLImate $climate 64 | */ 65 | public function prepareCli(CLImate $climate) 66 | { 67 | $cliPreparations = $this->getCliPreparations(); 68 | foreach ($cliPreparations as $cliPreparation) { 69 | $cliPreparation($climate); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Soy/Exception/CliArgumentDuplicationException.php: -------------------------------------------------------------------------------- 1 | component('%s', function () { 16 | // ... 17 | }); 18 | PHP; 19 | 20 | const MESSAGE_FORGOT_DEFAULT = <<<'PHP' 21 | component('default', null, ['my-component-1', 'my-component-2']); 24 | PHP; 25 | 26 | const MESSAGE_CALL_DIFFERENT_COMPONENT = '$ soy my-component'; 27 | 28 | const MESSAGE_PREPARE_RETURN = <<<'PHP' 29 | prepare(%1$s::class, function(%1$s $task) { 32 | return $task->setFoo('bar'); 33 | }); 34 | PHP; 35 | 36 | const MESSAGE_RETURN_RECIPE = <<<'PHP' 37 | output($climate); 68 | } else { 69 | $climate->error(get_class($exception) . ': ' . $exception->getMessage()); 70 | $climate->dim($exception->getTraceAsString())->br(); 71 | } 72 | 73 | if ($exception instanceof UnknownComponentException) { 74 | if ($exception->getComponent() === 'default') { 75 | $climate->lightYellow('Did you forget the default component?'); 76 | $climate->dim(self::MESSAGE_FORGOT_DEFAULT)->br(); 77 | } 78 | 79 | $climate->lightYellow('Did you forget to define the component?'); 80 | $climate->dim(sprintf(self::MESSAGE_DEFINE_COMPONENT, $exception->getComponent()))->br(); 81 | 82 | $climate->lightYellow('Did you mean to call a different component?'); 83 | $climate->dim(self::MESSAGE_CALL_DIFFERENT_COMPONENT)->br(); 84 | } elseif ($exception instanceof ReflectionException) { 85 | if (preg_match('/^Class .*Task does not exist$/', $exception->getMessage())) { 86 | $climate->lightYellow('Did you forget to include the task with composer?'); 87 | $climate->dim('$ composer require vendor/my-task')->br(); 88 | } 89 | } elseif ($exception instanceof FatalErrorException) { 90 | if (preg_match( 91 | '/^Argument \d+ passed to {closure}\(\) must be an instance of (.*), null given/', 92 | $exception->getMessage(), 93 | $matches 94 | )) { 95 | $climate->lightYellow('Did you forget to return the object in your preparation?'); 96 | $climate->dim(sprintf(self::MESSAGE_PREPARE_RETURN, $matches[1]))->br(); 97 | } elseif (preg_match( 98 | '/Undefined offset: 1 in (.*Parser\.php.*) on line \d+/', 99 | $exception->getMessage(), 100 | $matches 101 | )) { 102 | $climate->lightYellow('Did you use an argument, that expects a value, without a value?'); 103 | $climate->dim(self::MESSAGE_FLAG_WITHOUT_VALUE)->br(); 104 | } 105 | } elseif ($exception instanceof NoRecipeReturnedException) { 106 | $climate->lightYellow('Did you forget to return the recipe object in your recipe.php?'); 107 | $climate->dim(self::MESSAGE_RETURN_RECIPE)->br(); 108 | } elseif ($exception instanceof RecipeFileNotFoundException) { 109 | if ($exception->getRecipeFile() === Cli::DEFAULT_RECIPE_FILE) { 110 | $climate->lightYellow('Change to the right directory containing the recipe.php'); 111 | $climate->dim(self::MESSAGE_CHANGE_DIRECTORY)->br(); 112 | 113 | $climate->lightYellow('Execute soy with --recipe to define the location of the recipe.php'); 114 | $climate->dim(self::MESSAGE_SPECIFY_RECIPE)->br(); 115 | } else { 116 | $climate->lightYellow('This file doesn\'t seem to exist or isn\'t accessible:'); 117 | $climate->dim($exception->getRecipeFile())->br(); 118 | } 119 | } 120 | 121 | exit($exception->getCode() === 0 ? 255 : $exception->getCode()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Soy/Exception/FatalErrorException.php: -------------------------------------------------------------------------------- 1 | recipeFile = $recipeFile; 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getRecipeFile() 26 | { 27 | return $this->recipeFile; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Soy/Exception/SoyException.php: -------------------------------------------------------------------------------- 1 | error(get_class($this) . ': ' . $this->getMessage()); 12 | $climate->dim($this->getTraceAsString())->br(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Soy/Exception/UnknownComponentException.php: -------------------------------------------------------------------------------- 1 | component = $component; 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getComponent() 26 | { 27 | return $this->component; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Soy/Recipe.php: -------------------------------------------------------------------------------- 1 | preparations[$class])) { 33 | $this->preparations[$class] = []; 34 | } 35 | 36 | if ($prepend) { 37 | array_unshift($this->preparations[$class], $callable); 38 | } else { 39 | array_push($this->preparations[$class], $callable); 40 | } 41 | } 42 | 43 | /** 44 | * @param string $component 45 | * @param callable|null $callable 46 | * @param array $dependencies 47 | * @return Component 48 | */ 49 | public function component($component, callable $callable = null, array $dependencies = []) 50 | { 51 | $this->components[$component] = new Component($component, $callable); 52 | $this->dependencies[$component] = $dependencies; 53 | 54 | return $this->components[$component]; 55 | } 56 | 57 | /** 58 | * @return Component[] 59 | */ 60 | public function getComponents() 61 | { 62 | return $this->components; 63 | } 64 | 65 | /** 66 | * @param string $componentName 67 | * @return Component 68 | * @throws UnknownComponentException 69 | */ 70 | public function getComponent($componentName) 71 | { 72 | if (!array_key_exists($componentName, $this->components)) { 73 | throw new UnknownComponentException('Component ' . $componentName . ' not found'); 74 | } 75 | 76 | return $this->components[$componentName]; 77 | } 78 | 79 | /** 80 | * @return array 81 | */ 82 | public function getPreparations() 83 | { 84 | return $this->preparations; 85 | } 86 | 87 | /** 88 | * @return array 89 | */ 90 | public function getDependencies() 91 | { 92 | return $this->dependencies; 93 | } 94 | 95 | /** 96 | * @param callable $callable 97 | * @return $this 98 | */ 99 | public function cli(callable $callable) 100 | { 101 | $this->prepare(CLImate::class, function (CLImate $climate) use ($callable) { 102 | $callable($climate); 103 | return $climate; 104 | }); 105 | 106 | return $this; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Soy/Soy.php: -------------------------------------------------------------------------------- 1 | recipe = $recipe; 30 | } 31 | 32 | /** 33 | * @param string $componentName 34 | * @throws UnknownComponentException 35 | */ 36 | public function execute($componentName = 'default') 37 | { 38 | $container = $this->getContainer(); 39 | 40 | $this->traverseDependencies($componentName, function ($dependency) { 41 | $this->execute($dependency); 42 | }); 43 | 44 | $this->getRecipe()->getComponent($componentName)->execute($container); 45 | } 46 | 47 | public function prepare() 48 | { 49 | $containerBuilder = new ContainerBuilder(); 50 | 51 | foreach ($this->recipe->getPreparations() as $class => $callables) { 52 | foreach ($callables as $callable) { 53 | $containerBuilder->addDefinitions([ 54 | $class => \DI\decorate($callable) 55 | ]); 56 | } 57 | } 58 | 59 | $this->container = $containerBuilder->build(); 60 | } 61 | 62 | /** 63 | * @return Container 64 | */ 65 | public function getContainer() 66 | { 67 | if ($this->container === null) { 68 | throw new \LogicException('Recipe is not prepared'); 69 | } 70 | 71 | return $this->container; 72 | } 73 | 74 | /** 75 | * @return Recipe 76 | */ 77 | public function getRecipe() 78 | { 79 | return $this->recipe; 80 | } 81 | 82 | /** 83 | * @param CLImate $climate 84 | * @param string $componentName 85 | * @throws UnknownComponentException 86 | */ 87 | public function prepareCli(CLImate $climate, $componentName) 88 | { 89 | $component = $this->getRecipe()->getComponent($componentName); 90 | 91 | $this->traverseDependencies($componentName, function ($componentName) use ($climate) { 92 | $this->getRecipe()->getComponent($componentName)->prepareCli($climate); 93 | }); 94 | 95 | $component->prepareCli($climate); 96 | 97 | $climate->arguments->parse(); 98 | } 99 | 100 | /** 101 | * @param string $componentName 102 | * @param callable $callable 103 | */ 104 | private function traverseDependencies($componentName, callable $callable) 105 | { 106 | array_walk($this->recipe->getDependencies()[$componentName], function ($dependency) use ($callable) { 107 | $callable($dependency); 108 | $this->traverseDependencies($dependency, $callable); 109 | }); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Soy/Task/CliTask.php: -------------------------------------------------------------------------------- 1 | climate = $climate; 41 | } 42 | 43 | public function run() 44 | { 45 | $command = $this->getCommand(); 46 | 47 | if ($this->isVerbose()) { 48 | $this->climate->lightBlue('$ ' . $command); 49 | } 50 | 51 | exec($command, $output, $exitCode); 52 | 53 | if ($this->isVerbose()) { 54 | $this->climate->dim(implode(PHP_EOL, $output)); 55 | } 56 | 57 | if ($exitCode !== 0 && $this->shouldThrowExceptionOnError()) { 58 | throw new CliTaskException('Non-zero exit code: ' . $exitCode); 59 | } 60 | } 61 | 62 | /** 63 | * @param string $binary 64 | * @return $this 65 | */ 66 | public function setBinary($binary) 67 | { 68 | $this->binary = $binary; 69 | return $this; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getBinary() 76 | { 77 | return $this->binary; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getCommand() 84 | { 85 | $command = $this->getBinary(); 86 | 87 | if (count($this->getArguments()) > 0) { 88 | $command .= ' ' . implode(' ', $this->getArguments()); 89 | } 90 | 91 | return $command; 92 | } 93 | 94 | /** 95 | * @return bool 96 | */ 97 | public function isVerbose() 98 | { 99 | return $this->verbose; 100 | } 101 | 102 | /** 103 | * @param bool $verbose 104 | * @return $this 105 | */ 106 | public function setVerbose($verbose) 107 | { 108 | $this->verbose = $verbose; 109 | return $this; 110 | } 111 | 112 | /** 113 | * @return bool 114 | */ 115 | public function shouldThrowExceptionOnError() 116 | { 117 | return $this->throwExceptionOnError; 118 | } 119 | 120 | /** 121 | * @param bool $throwExceptionOnError 122 | * @return $this 123 | */ 124 | public function setThrowExceptionOnError($throwExceptionOnError) 125 | { 126 | $this->throwExceptionOnError = $throwExceptionOnError; 127 | return $this; 128 | } 129 | 130 | /** 131 | * @param string $argument 132 | * @return $this 133 | */ 134 | public function addArgument($argument) 135 | { 136 | $this->arguments[] = $argument; 137 | return $this; 138 | } 139 | 140 | /** 141 | * @param array $arguments 142 | * @return $this 143 | */ 144 | public function setArguments(array $arguments) 145 | { 146 | $this->arguments = $arguments; 147 | return $this; 148 | } 149 | 150 | /** 151 | * @return array 152 | */ 153 | public function getArguments() 154 | { 155 | return $this->arguments; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Soy/Task/TaskInterface.php: -------------------------------------------------------------------------------- 1 |