Overview

Namespaces

  • Charcoal
    • App
      • Action
      • Config
      • Handler
      • Middleware
      • Module
      • Route
      • Script
      • ServiceProvider
      • Template

Classes

  • Charcoal\App\Action\AbstractAction
  • Charcoal\App\App
  • Charcoal\App\AppConfig
  • Charcoal\App\AppContainer
  • Charcoal\App\Config\CacheConfig
  • Charcoal\App\Config\DatabaseConfig
  • Charcoal\App\Config\FilesystemConfig
  • Charcoal\App\Config\LoggerConfig
  • Charcoal\App\Config\MemcacheCacheConfig
  • Charcoal\App\Config\MemcacheCacheServerConfig
  • Charcoal\App\Handler\AbstractHandler
  • Charcoal\App\Handler\Error
  • Charcoal\App\Handler\HandlerConfig
  • Charcoal\App\Handler\NotAllowed
  • Charcoal\App\Handler\NotFound
  • Charcoal\App\Handler\PhpError
  • Charcoal\App\Handler\Shutdown
  • Charcoal\App\Middleware\CacheMiddleware
  • Charcoal\App\Module\AbstractModule
  • Charcoal\App\Module\ModuleConfig
  • Charcoal\App\Module\ModuleManager
  • Charcoal\App\Route\ActionRoute
  • Charcoal\App\Route\ActionRouteConfig
  • Charcoal\App\Route\RouteConfig
  • Charcoal\App\Route\RouteManager
  • Charcoal\App\Route\ScriptRoute
  • Charcoal\App\Route\ScriptRouteConfig
  • Charcoal\App\Route\TemplateRoute
  • Charcoal\App\Route\TemplateRouteConfig
  • Charcoal\App\Script\AbstractScript
  • Charcoal\App\ServiceProvider\AppServiceProvider
  • Charcoal\App\ServiceProvider\CacheServiceProvider
  • Charcoal\App\ServiceProvider\DatabaseServiceProvider
  • Charcoal\App\ServiceProvider\FilesystemServiceProvider
  • Charcoal\App\ServiceProvider\LoggerServiceProvider
  • Charcoal\App\ServiceProvider\ViewServiceProvider
  • Charcoal\App\Template\AbstractTemplate
  • Charcoal\App\Template\AbstractWidget
  • Charcoal\App\Template\WidgetBuilder

Interfaces

  • Charcoal\App\Action\ActionInterface
  • Charcoal\App\AppAwareInterface
  • Charcoal\App\Handler\HandlerInterface
  • Charcoal\App\Module\ModuleInterface
  • Charcoal\App\Route\RouteInterface
  • Charcoal\App\Script\CronScriptInterface
  • Charcoal\App\Script\ScriptInterface
  • Charcoal\App\Template\TemplateInterface
  • Charcoal\App\Template\WidgetInterface

Traits

  • Charcoal\App\AppAwareTrait
  • Charcoal\App\CallableResolverAwareTrait
  • Charcoal\App\Script\ArgScriptTrait
  • Charcoal\App\Script\CronScriptTrait
  • Charcoal\App\Script\PathScriptTrait
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Charcoal\App\Script;
  4: 
  5: use \InvalidArgumentException;
  6: use \RuntimeException;
  7: 
  8: // From PSR-3
  9: use \Psr\Log\LoggerAwareInterface;
 10: use \Psr\Log\LoggerAwareTrait;
 11: 
 12: // From PSR-7
 13: use \Psr\Http\Message\RequestInterface;
 14: use \Psr\Http\Message\ResponseInterface;
 15: 
 16: // From Pimple
 17: use \Pimple\Container;
 18: 
 19: // From 'league/climate'
 20: use \League\CLImate\CLImate;
 21: use \League\CLImate\Util\Reader\ReaderInterface;
 22: 
 23: // From 'charcoal-config'
 24: use \Charcoal\Config\AbstractEntity;
 25: 
 26: // From 'charcoal-app'
 27: use \Charcoal\App\AppInterface;
 28: use \Charcoal\App\Script\ScriptInterface;
 29: 
 30: /**
 31:  * Abstract CLI Script
 32:  */
 33: abstract class AbstractScript extends AbstractEntity implements
 34:     LoggerAwareInterface,
 35:     ScriptInterface
 36: {
 37:     use LoggerAwareTrait;
 38: 
 39:     /**
 40:      * @var string $ident
 41:      */
 42:     private $ident;
 43: 
 44:     /**
 45:      * @var string $description
 46:      */
 47:     private $description;
 48: 
 49:     /**
 50:      * @var array $arguments
 51:      */
 52:     private $arguments;
 53: 
 54:     /**
 55:      * @var CLImate $climate
 56:      */
 57:     private $climate;
 58: 
 59:     /**
 60:      * @var ReaderInterface $cliamteReader
 61:      */
 62:     private $climateReader;
 63: 
 64:     /**
 65:      * @var boolean $quiet
 66:      */
 67:     private $quiet = false;
 68: 
 69:     /**
 70:      * @var boolean $verbose
 71:      */
 72:     private $verbose = false;
 73: 
 74:     /**
 75:      * @var boolean $interactive
 76:      */
 77:     private $interactive = false;
 78: 
 79:     /**
 80:      * @var boolean $dryRun
 81:      */
 82:     private $dryRun = false;
 83: 
 84:     /**
 85:      * Return a new CLI script.
 86:      *
 87:      * @param array|\ArrayAccess $data The dependencies (app and logger).
 88:      */
 89:     public function __construct($data = null)
 90:     {
 91:         $this->setLogger($data['logger']);
 92:         $this->setClimate($data['climate']);
 93:         if (isset($data['climate_reader'])) {
 94:             $this->setClimateReader($data['climate_reader']);
 95:         }
 96: 
 97:         if (isset($data['container'])) {
 98:             $this->setDependencies($data['container']);
 99:         }
100:     }
101: 
102:     /**
103:      * Give an opportunity to children classes to inject dependencies from a Pimple Container.
104:      *
105:      * Does nothing by default, reimplement in children classes.
106:      *
107:      * The `$container` DI-container (from `Pimple`) should not be saved or passed around, only to be used to
108:      * inject dependencies (typically via setters).
109:      *
110:      * @param  Container $container A dependencies container instance.
111:      * @return void
112:      */
113:     public function setDependencies(Container $container)
114:     {
115:         // This method is a stub. Reimplement in children template classes.
116:     }
117: 
118:     /**
119:      * @return void
120:      */
121:     protected function init()
122:     {
123:         $arguments = $this->defaultArguments();
124:         $this->setArguments($arguments);
125:     }
126: 
127:     /**
128:      * @param  RequestInterface  $request  A PSR-7 compatible Request instance.
129:      * @param  ResponseInterface $response A PSR-7 compatible Response instance.
130:      * @return ResponseInterface
131:      */
132:     final public function __invoke(RequestInterface $request, ResponseInterface $response)
133:     {
134:         $this->init();
135: 
136:         $climate   = $this->climate();
137:         $arguments = $climate->arguments;
138: 
139:         if ($arguments->defined('help')) {
140:             $climate->usage();
141:             return $response;
142:         }
143: 
144:         if ($arguments->defined('quiet') && $arguments->defined('verbose')) {
145:             $climate->error('You must choose one of --quiet or --verbose');
146:             return $response;
147:         }
148: 
149:         if ($arguments->defined('quiet')) {
150:             $this->setQuiet(true);
151:         }
152: 
153:         if ($arguments->defined('verbose')) {
154:             $this->setVerbose(true);
155:         }
156: 
157:         if ($arguments->defined('interactive')) {
158:             $this->setInteractive(true);
159:         }
160: 
161:         if ($arguments->defined('dry_run')) {
162:             $this->setDryRun(true);
163:         }
164: 
165:         $arguments->parse();
166: 
167:         return $this->run($request, $response);
168:     }
169: 
170:     /**
171:      * @param CLImate $climate A climate instance.
172:      * @return void
173:      */
174:     private function setClimate(CLImate $climate)
175:     {
176:         $this->climate = $climate;
177:     }
178: 
179:     /**
180:      * Safe climate getter.
181:      * If the instance was not previously set, create it.
182:      *
183:      * > CLImate is "PHP's best friend for the terminal."
184:      * > "CLImate allows you to easily output colored text, special formats, and more."
185:      *
186:      * @return CLImate
187:      */
188:     protected function climate()
189:     {
190:         return $this->climate;
191:     }
192: 
193:     /**
194:      * @param ReaderInterface $climateReader A climate reader.
195:      * @return void
196:      */
197:     private function setClimateReader(ReaderInterface $climateReader)
198:     {
199:         $this->climateReader = $climateReader;
200:     }
201: 
202:     /**
203:      * @return ReaderInterface
204:      */
205:     protected function climateReader()
206:     {
207:         return $this->climateReader;
208:     }
209: 
210:     /**
211:      * Retrieve the script's supported arguments.
212:      *
213:      * @return array
214:      */
215:     public function defaultArguments()
216:     {
217:         return [
218:             'help' => [
219:                 'prefix'       => 'h',
220:                 'longPrefix'   => 'help',
221:                 'noValue'      => true,
222:                 'description'  => 'Display help information.'
223:             ],
224:             'quiet' => [
225:                 'prefix'       => 'q',
226:                 'longPrefix'   => 'quiet',
227:                 'noValue'      => true,
228:                 'description'  => 'Only print error and warning messages.'
229:             ],
230:             'verbose' => [
231:                 'prefix'        => 'v',
232:                 'longPrefix'    => 'verbose',
233:                 'noValue'       => true,
234:                 'description'   => 'Increase verbosity of messages.'
235:             ],
236:             'interactive' => [
237:                 'prefix'       => 'i',
238:                 'longPrefix'   => 'interactive',
239:                 'noValue'      => true,
240:                 'description'  => 'Ask any interactive question.'
241:             ],
242:             'dry_run' => [
243:                 'longPrefix'   => 'dry-run',
244:                 'noValue'      => true,
245:                 'description'  => 'This will simulate the script and show you what would happen.'
246:             ]
247:         ];
248:     }
249: 
250:     /**
251:      * @param string $ident The script identifier string.
252:      * @throws InvalidArgumentException If the ident argument is not a string.
253:      * @return ScriptInterface Chainable
254:      */
255:     public function setIdent($ident)
256:     {
257:         if (!is_string($ident)) {
258:             throw new InvalidArgumentException(
259:                 'Ident must be a string'
260:             );
261:         }
262:         $this->ident = $ident;
263:         return $this;
264:     }
265: 
266:     /**
267:      * @return string
268:      */
269:     public function ident()
270:     {
271:         return $this->ident;
272:     }
273: 
274:     /**
275:      * @param string $description The script description.
276:      * @throws InvalidArgumentException If the deescription parameter is not a string.
277:      * @return ScriptInterface Chainable
278:      */
279:     public function setDescription($description)
280:     {
281:         if (!is_string($description)) {
282:             throw new InvalidArgumentException(
283:                 'Description must be a string'
284:             );
285:         }
286:         $this->description = $description;
287:         $this->climate()->description($description);
288:         return $this;
289:     }
290: 
291:     /**
292:      * @return string
293:      */
294:     public function description()
295:     {
296:         return $this->description;
297:     }
298: 
299:     /**
300:      * @param boolean $quiet The quiet flag.
301:      * @return ScriptInterface Chainable
302:      */
303:     public function setQuiet($quiet)
304:     {
305:         $this->quiet = !!$quiet;
306:         return $this;
307:     }
308: 
309:     /**
310:      * @return boolean
311:      */
312:     public function quiet()
313:     {
314:         return $this->quiet;
315:     }
316: 
317:     /**
318:      * @param boolean $verbose The verbose flag.
319:      * @return ScriptInterface Chainable
320:      */
321:     public function setVerbose($verbose)
322:     {
323:         $this->verbose = !!$verbose;
324:         return $this;
325:     }
326: 
327:     /**
328:      * @return boolean
329:      */
330:     public function verbose()
331:     {
332:         return $this->verbose;
333:     }
334: 
335:     /**
336:      * @param boolean $interactive The interactive flag.
337:      * @return ScriptInterface Chainable
338:      */
339:     public function setInteractive($interactive)
340:     {
341:         $this->interactive = !!$interactive;
342:         return $this;
343:     }
344: 
345:     /**
346:      * @return boolean
347:      */
348:     public function interactive()
349:     {
350:         return $this->interactive;
351:     }
352: 
353:     /**
354:      * @param boolean $simulate The dry-run flag.
355:      * @return ScriptInterface Chainable
356:      */
357:     public function setDryRun($simulate)
358:     {
359:         $this->dryRun = !!$simulate;
360:         return $this;
361:     }
362: 
363:     /**
364:      * @return boolean
365:      */
366:     public function dryRun()
367:     {
368:         return $this->dryRun;
369:     }
370: 
371:     /**
372:      * @param array $arguments The scripts argument array, as [key=>value].
373:      * @return ScriptInterface Chainable
374:      */
375:     public function setArguments(array $arguments)
376:     {
377:         $this->arguments = [];
378:         foreach ($arguments as $argumentIdent => $argument) {
379:             $this->addArgument($argumentIdent, $argument);
380:         }
381: 
382:         return $this;
383:     }
384: 
385:     /**
386:      * @param string $argumentIdent The argument identifier.
387:      * @param array  $argument      The argument options.
388:      * @throws InvalidArgumentException If the argument ident is not a string.
389:      * @return ScriptInterface Chainable
390:      */
391:     public function addArgument($argumentIdent, array $argument)
392:     {
393:         if (!is_string($argumentIdent)) {
394:             throw new InvalidArgumentException(
395:                 'Argument ident must be a string.'
396:             );
397:         }
398:         $this->arguments[$argumentIdent] = $argument;
399:         $this->climate()->arguments->add([$argumentIdent=>$argument]);
400:         return $this;
401:     }
402: 
403:     /**
404:      * @return array $arguments
405:      */
406:     public function arguments()
407:     {
408:         return $this->arguments;
409:     }
410: 
411:     /**
412:      * @param string $argumentIdent The argument identifier to retrieve options from.
413:      * @return array|null The argument options, or null if it does not exist.
414:      */
415:     public function argument($argumentIdent)
416:     {
417:         if (!isset($this->arguments[$argumentIdent])) {
418:             return null;
419:         }
420:         return $this->arguments[$argumentIdent];
421:     }
422: 
423:     /**
424:      * Retrieve an argument either from argument list (if set) or from user input.
425:      *
426:      * @param  string $argName An argument identifier.
427:      * @return mixed Returns the argument or prompt value.
428:      */
429:     protected function argOrInput($argName)
430:     {
431:         $climate = $this->climate();
432: 
433:         $value = $climate->arguments->get($argName);
434:         if ($value) {
435:             return $value;
436:         }
437: 
438:         return $this->input($argName);
439:     }
440: 
441:     /**
442:      * Request a value from the user for the given argument.
443:      *
444:      * @param  string $name An argument identifier.
445:      * @throws RuntimeException If a radio or checkbox prompt has no options.
446:      * @return mixed Returns the prompt value.
447:      */
448:     protected function input($name)
449:     {
450:         $cli = $this->climate();
451:         $arg = $this->argument($name);
452: 
453:         if ($arg) {
454:             $type = (isset($arg['inputType']) ? $arg['inputType'] : 'input');
455: 
456:             if (isset($arg['prompt'])) {
457:                 $label = $arg['prompt'];
458:             } elseif (isset($arg['description'])) {
459:                 $label = $arg['description'];
460:             } else {
461:                 $label = $name;
462:             }
463: 
464:             if (isset($arg['choices'])) {
465:                 $arg['options'] = $arg['choices'];
466:                 $arg['acceptValue'] = $arg['choices'];
467:             }
468: 
469:             $accept = true;
470:         } else {
471:             $type   = 'input';
472:             $label  = $name;
473:             $accept = false;
474:         }
475: 
476:         $prompt = 'prompt';
477:         switch ($type) {
478:             case 'checkboxes':
479:             case 'radio':
480:                 if (!isset($arg['options'])) {
481:                     throw new RuntimeException(
482:                         sprintf('The [%s] argument has no options.', $key)
483:                     );
484:                 }
485: 
486:                 $accept = false;
487:                 $input  = $cli->{$type}($label, $arg['options'], $this->climateReader);
488:                 break;
489: 
490:             case 'confirm':
491:                 $prompt = 'confirmed';
492:                 $input  = $cli->confirm($label, $this->climateReader);
493:                 break;
494: 
495:             case 'password':
496:                 $input = $cli->password($label, $this->climateReader);
497:                 $input->multiLine();
498:                 break;
499: 
500:             case 'multiline':
501:                 $input = $cli->input($label, $this->climateReader);
502:                 $input->multiLine();
503:                 break;
504: 
505:             default:
506:                 $input = $cli->input($label, $this->climateReader);
507:                 break;
508:         }
509: 
510:         if ($accept) {
511:             if (isset($arg['acceptValue'])) {
512:                 if (is_array($arg['acceptValue']) || is_callable($arg['acceptValue'])) {
513:                     $input->accept($arg['acceptValue']);
514:                 }
515:             }
516:         }
517: 
518:         return $input->{$prompt}();
519:     }
520: }
521: 
API documentation generated by ApiGen