PK c]GMϼ½‹ä ä phpunit.travis.xmlnu W+A„¶
./tests/Assetic/Test/
./src/Assetic/
PK c]GMÿ(÷(¾ ¾ .travis.ymlnu W+A„¶ language: php
php:
- 5.3
- 5.4
before_script:
- git clone https://github.com/kamicane/packager.git vendor/packager --quiet --depth 1
- git clone https://github.com/leafo/lessphp.git vendor/lessphp --quiet --depth 1
- git clone https://github.com/fabpot/Twig.git vendor/twig --quiet --depth 1
- svn checkout http://cssmin.googlecode.com/svn/trunk/ vendor/cssmin --quiet
script: phpunit --configuration phpunit.travis.xmlPK c]GMWá-µ µ CHANGELOG-1.0.mdnu W+A„¶ 1.0.3 (March 2, 2012)
---------------------
* Added "boring" option to Compass filter
* Fixed accumulation of load paths in Compass filter
* Fixed issues in CssImport and CssRewrite filters
1.0.2 (August 26, 2011)
-----------------------
* Twig 1.2 compatibility
* Fixed filtering of large LessCSS assets
* Fixed escaping of commands on Windows
* Misc fixes to Compass filter
* Removed default CssEmbed charset
1.0.1 (July 15, 2011)
---------------------
* Fixed Twig error handling
* Removed use of STDIN
* Added inheritance of environment variables
* Fixed Compass on Windows
* Improved escaping of commands
1.0.0 (July 10, 2011)
---------------------
* Initial release
PK c]GMv¡ŸG7 7 README.mdnu W+A„¶ # Assetic ![project status](http://stillmaintained.com/kriswallsmith/assetic.png) #
Assetic is an asset management framework for PHP.
``` php
dump();
```
Assets
------
An Assetic asset is something with filterable content that can be loaded and
dumped. An asset also includes metadata, some of which can be manipulated and
some of which is immutable.
| **Property** | **Accessor** | **Mutator** |
|--------------|-----------------|---------------|
| content | getContent | setContent |
| mtime | getLastModified | n/a |
| source root | getSourceRoot | n/a |
| source path | getSourcePath | n/a |
| target path | getTargetPath | setTargetPath |
Filters
-------
Filters can be applied to manipulate assets.
``` php
dump();
```
The filters applied to the collection will cascade to each asset leaf if you
iterate over it.
``` php
dump();
}
```
The core provides the following filters in the `Assetic\Filter` namespace:
* `CoffeeScriptFilter`: compiles CoffeeScript into Javascript
* `CssEmbedFilter`: embeds image data in your stylesheets
* `CssImportFilter`: inlines imported stylesheets
* `CssMinFilter`: minifies CSS
* `CssRewriteFilter`: fixes relative URLs in CSS assets when moving to a new URL
* `GoogleClosure\CompilerApiFilter`: compiles Javascript using the Google Closure Compiler API
* `GoogleClosure\CompilerJarFilter`: compiles Javascript using the Google Closure Compiler JAR
* `JpegoptimFilter`: optimize your JPEGs
* `JpegtranFilter`: optimize your JPEGs
* `LessFilter`: parses LESS into CSS (using less.js with node.js)
* `LessphpFilter`: parses LESS into CSS (using lessphp)
* `OptiPngFilter`: optimize your PNGs
* `PngoutFilter`: optimize your PNGs
* `CompassFilter`: Compass CSS authoring framework
* `Sass\SassFilter`: parses SASS into CSS
* `Sass\ScssFilter`: parses SCSS into CSS
* `SprocketsFilter`: Sprockets Javascript dependency management
* `StylusFilter`: parses STYL into CSS
* `Yui\CssCompressorFilter`: compresses CSS using the YUI compressor
* `Yui\JsCompressorFilter`: compresses Javascript using the YUI compressor
Asset Manager
-------------
An asset manager is provided for organizing assets.
``` php
set('jquery', new FileAsset('/path/to/jquery.js'));
$am->set('base_css', new GlobAsset('/path/to/css/*'));
```
The asset manager can also be used to reference assets to avoid duplication.
``` php
set('my_plugin', new AssetCollection(array(
new AssetReference($am, 'jquery'),
new FileAsset('/path/to/jquery.plugin.js'),
)));
```
Filter Manager
--------------
A filter manager is also provided for organizing filters.
``` php
set('sass', new SassFilter('/path/to/parser/sass'));
$fm->set('yui_css', new Yui\CssCompressorFilter('/path/to/yuicompressor.jar'));
```
Asset Factory
-------------
If you'd rather not create all these objects by hand, you can use the asset
factory, which will do most of the work for you.
``` php
setAssetManager($am);
$factory->setFilterManager($fm);
$factory->setDebug(true);
$css = $factory->createAsset(array(
'@reset', // load the asset manager's "reset" asset
'css/src/*.scss', // load every scss files from "/path/to/asset/directory/css/src/"
), array(
'scss', // filter through the filter manager's "scss" filter
'?yui_css', // don't use this filter in debug mode
));
echo $css->dump();
```
Prefixing a filter name with a question mark, as `yui_css` is here, will cause
that filter to be omitted when the factory is in debug mode.
Caching
-------
A simple caching mechanism is provided to avoid unnecessary work.
``` php
dump();
$js->dump();
$js->dump();
```
Static Assets
-------------
Alternatively you can just write filtered assets to your web directory and be
done with it.
``` php
writeManagerAssets($am);
```
Twig
----
To use the Assetic [Twig][3] extension you must register it to your Twig
environment:
``` php
addExtension(new AsseticExtension($factory, $debug));
```
Once in place, the extension exposes a stylesheets and a javascripts tag with a syntax similar
to what the asset factory uses:
``` html+jinja
{% stylesheets '/path/to/sass/main.sass' filter='sass,?yui_css' output='css/all.css' %}
{% endstylesheets %}
```
This example will render one `link` element on the page that includes a URL
where the filtered asset can be found.
When the extension is in debug mode, this same tag will render multiple `link`
elements, one for each asset referenced by the `css/src/*.sass` glob. The
specified filters will still be applied, unless they are marked as optional
using the `?` prefix.
This behavior can also be triggered by setting a `debug` attribute on the tag:
``` html+jinja
{% stylesheets 'css/*' debug=true %} ... {% stylesheets %}
```
These assets need to be written to the web directory so these URLs don't
return 404 errors.
``` php
setLoader('twig', new TwigFormulaLoader($twig));
// loop through all your templates
foreach ($templates as $template) {
$resource = new TwigResource($twigLoader, $template);
$am->addResource($resource, 'twig');
}
$writer = new AssetWriter('/path/to/web');
$writer->writeManagerAssets($am);
```
---
Assetic is based on the Python [webassets][1] library (available on
[GitHub][2]).
[1]: http://elsdoerfer.name/docs/webassets
[2]: https://github.com/miracle2k/webassets
[3]: http://www.twig-project.org
PK c]GM¿/k
.gitignorenu W+A„¶ phpunit.xml
vendor/
PK c]GM˜
!ö ö src/functions.phpnu W+A„¶ factory = $factory;
}
/**
* Returns an array of javascript URLs.
*
* @param array|string $inputs Input strings
* @param array|string $filters Filter names
* @param array $options An array of options
*
* @return array An array of javascript URLs
*/
function assetic_javascripts($inputs = array(), $filters = array(), array $options = array())
{
if (!isset($options['output'])) {
$options['output'] = 'js/*.js';
}
return _assetic_urls($inputs, $filters, $options);
}
/**
* Returns an array of stylesheet URLs.
*
* @param array|string $inputs Input strings
* @param array|string $filters Filter names
* @param array $options An array of options
*
* @return array An array of stylesheet URLs
*/
function assetic_stylesheets($inputs = array(), $filters = array(), array $options = array())
{
if (!isset($options['output'])) {
$options['output'] = 'css/*.css';
}
return _assetic_urls($inputs, $filters, $options);
}
/**
* Returns an image URL.
*
* @param string $input An input
* @param array|string $filters Filter names
* @param array $options An array of options
*
* @return string An image URL
*/
function assetic_image($input, $filters = array(), array $options = array())
{
if (!isset($options['output'])) {
$options['output'] = 'images/*';
}
$urls = _assetic_urls($input, $filters, $options);
return current($urls);
}
/**
* Returns an array of asset urls.
*
* @param array|string $inputs Input strings
* @param array|string $filters Filter names
* @param array $options An array of options
*
* @return array An array of URLs
*/
function _assetic_urls($inputs = array(), $filters = array(), array $options = array())
{
global $_assetic;
if (!is_array($inputs)) {
$inputs = array_filter(array_map('trim', explode(',', $inputs)));
}
if (!is_array($filters)) {
$filters = array_filter(array_map('trim', explode(',', $filters)));
}
$coll = $_assetic->factory->createAsset($inputs, $filters, $options);
$debug = isset($options['debug']) ? $options['debug'] : $_assetic->factory->isDebug();
$combine = isset($options['combine']) ? $options['combine'] : !$debug;
$one = $coll->getTargetPath();
if ($combine) {
$many = array();
foreach ($coll as $leaf) {
$many[] = $leaf->getTargetPath();
}
} else {
$many = array($one);
}
return new TraversableString($one, $many);
}
PK c]GMƒõ׳) ) src/Assetic/AssetWriter.phpnu W+A„¶
*/
class AssetWriter
{
private $dir;
/**
* Constructor.
*
* @param string $dir The base web directory
*/
public function __construct($dir)
{
$this->dir = $dir;
}
public function writeManagerAssets(AssetManager $am)
{
foreach ($am->getNames() as $name) {
$this->writeAsset($am->get($name));
}
}
public function writeAsset(AssetInterface $asset)
{
static::write($this->dir . '/' . $asset->getTargetPath(), $asset->dump());
}
static protected function write($path, $contents)
{
if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) {
throw new \RuntimeException('Unable to create directory '.$dir);
}
if (false === @file_put_contents($path, $contents)) {
throw new \RuntimeException('Unable to write file '.$path);
}
}
}
PK c]GMï©ü˜Õ Õ 1 src/Assetic/Extension/Twig/AsseticTokenParser.phpnu W+A„¶ factory = $factory;
$this->tag = $tag;
$this->output = $output;
$this->single = $single;
$this->extensions = $extensions;
}
public function parse(\Twig_Token $token)
{
$inputs = array();
$filters = array();
$name = null;
$attributes = array(
'output' => $this->output,
'var_name' => 'asset_url',
);
$stream = $this->parser->getStream();
while (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
if ($stream->test(\Twig_Token::STRING_TYPE)) {
// '@jquery', 'js/src/core/*', 'js/src/extra.js'
$inputs[] = $stream->next()->getValue();
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'filter')) {
// filter='yui_js'
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$filters = array_merge($filters, array_filter(array_map('trim', explode(',', $stream->expect(\Twig_Token::STRING_TYPE)->getValue()))));
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'output')) {
// output='js/packed/*.js' OR output='js/core.js'
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$attributes['output'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'name')) {
// name='core_js'
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$name = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'as')) {
// as='the_url'
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$attributes['var_name'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'debug')) {
// debug=true
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$attributes['debug'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue();
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'combine')) {
// combine=true
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$attributes['combine'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue();
} elseif ($stream->test(\Twig_Token::NAME_TYPE, $this->extensions)) {
// an arbitrary configured attribute
$key = $stream->next()->getValue();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$attributes[$key] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
} else {
$token = $stream->getCurrent();
throw new \Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', \Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
}
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$endtag = 'end'.$this->getTag();
$test = function(\Twig_Token $token) use($endtag) { return $token->test($endtag); };
$body = $this->parser->subparse($test, true);
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
if ($this->single && 1 < count($inputs)) {
$inputs = array_slice($inputs, -1);
}
if (!$name) {
$name = $this->factory->generateAssetName($inputs, $filters, $attributes);
}
$asset = $this->factory->createAsset($inputs, $filters, $attributes + array('name' => $name));
return $this->createNode($asset, $body, $inputs, $filters, $name, $attributes, $token->getLine(), $this->getTag());
}
public function getTag()
{
return $this->tag;
}
protected function createNode(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null)
{
return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag);
}
}
PK c]GM:᪙ ™ 4 src/Assetic/Extension/Twig/AsseticFilterFunction.phpnu W+A„¶ filter = $filter;
parent::__construct($options);
}
public function compile()
{
return sprintf('$this->env->getExtension(\'assetic\')->getFilterInvoker(\'%s\')->invoke', $this->filter);
}
}
PK c]GM”¼]Ä Ä * src/Assetic/Extension/Twig/AsseticNode.phpnu W+A„¶ $body);
$attributes = array_replace(
array('debug' => null, 'combine' => null, 'var_name' => 'asset_url'),
$attributes,
array('asset' => $asset, 'inputs' => $inputs, 'filters' => $filters, 'name' => $name)
);
parent::__construct($nodes, $attributes, $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
$combine = $this->getAttribute('combine');
$debug = $this->getAttribute('debug');
if (null === $combine && null !== $debug) {
$combine = !$debug;
}
if (null === $combine) {
$compiler
->write("if (isset(\$context['assetic']['debug']) && \$context['assetic']['debug']) {\n")
->indent()
;
$this->compileDebug($compiler);
$compiler
->outdent()
->write("} else {\n")
->indent()
;
$this->compileAsset($compiler, $this->getAttribute('asset'), $this->getAttribute('name'));
$compiler
->outdent()
->write("}\n")
;
} elseif ($combine) {
$this->compileAsset($compiler, $this->getAttribute('asset'), $this->getAttribute('name'));
} else {
$this->compileDebug($compiler);
}
$compiler
->write('unset($context[')
->repr($this->getAttribute('var_name'))
->raw("]);\n")
;
}
protected function compileDebug(\Twig_Compiler $compiler)
{
$i = 0;
foreach ($this->getAttribute('asset') as $leaf) {
$leafName = $this->getAttribute('name').'_'.$i++;
$this->compileAsset($compiler, $leaf, $leafName);
}
}
protected function compileAsset(\Twig_Compiler $compiler, AssetInterface $asset, $name)
{
$compiler
->write("// asset \"$name\"\n")
->write('$context[')
->repr($this->getAttribute('var_name'))
->raw('] = ')
;
$this->compileAssetUrl($compiler, $asset, $name);
$compiler
->raw(";\n")
->subcompile($this->getNode('body'))
;
}
protected function compileAssetUrl(\Twig_Compiler $compiler, AssetInterface $asset, $name)
{
$compiler->repr($asset->getTargetPath());
}
}
PK c]GMÕ î6 6 / src/Assetic/Extension/Twig/AsseticExtension.phpnu W+A„¶ factory = $factory;
$this->functions = array();
foreach ($functions as $function => $options) {
if (is_integer($function) && is_string($options)) {
$this->functions[$options] = array('filter' => $options);
} else {
$this->functions[$function] = $options + array('filter' => $function);
}
}
}
public function getTokenParsers()
{
return array(
new AsseticTokenParser($this->factory, 'javascripts', 'js/*.js'),
new AsseticTokenParser($this->factory, 'stylesheets', 'css/*.css'),
new AsseticTokenParser($this->factory, 'image', 'images/*', true),
);
}
public function getFunctions()
{
$functions = array();
foreach ($this->functions as $function => $filter) {
$functions[$function] = new AsseticFilterFunction($function);
}
return $functions;
}
public function getGlobals()
{
return array(
'assetic' => array('debug' => $this->factory->isDebug()),
);
}
public function getFilterInvoker($function)
{
return new AsseticFilterInvoker($this->factory, $this->functions[$function]);
}
public function getName()
{
return 'assetic';
}
}
PK c]GMmÌÈÕx x + src/Assetic/Extension/Twig/TwigResource.phpnu W+A„¶
*/
class TwigResource implements ResourceInterface
{
private $loader;
private $name;
public function __construct(\Twig_LoaderInterface $loader, $name)
{
$this->loader = $loader;
$this->name = $name;
}
public function getContent()
{
try {
return $this->loader->getSource($this->name);
} catch (\Twig_Error_Loader $e) {
return '';
}
}
public function isFresh($timestamp)
{
try {
return $this->loader->isFresh($this->name, $timestamp);
} catch (\Twig_Error_Loader $e) {
return false;
}
}
public function __toString()
{
return $this->name;
}
}
PK c]GMÐzR; ; 0 src/Assetic/Extension/Twig/TwigFormulaLoader.phpnu W+A„¶
*/
class TwigFormulaLoader implements FormulaLoaderInterface
{
private $twig;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig;
}
public function load(ResourceInterface $resource)
{
try {
$tokens = $this->twig->tokenize($resource->getContent(), (string) $resource);
$nodes = $this->twig->parse($tokens);
} catch (\Exception $e) {
return array();
}
return $this->loadNode($nodes);
}
/**
* Loads assets from the supplied node.
*
* @return array An array of asset formulae indexed by name
*/
private function loadNode(\Twig_Node $node)
{
$formulae = array();
if ($node instanceof AsseticNode) {
$formulae[$node->getAttribute('name')] = array(
$node->getAttribute('inputs'),
$node->getAttribute('filters'),
array(
'output' => $node->getAttribute('asset')->getTargetPath(),
'name' => $node->getAttribute('name'),
'debug' => $node->getAttribute('debug'),
'combine' => $node->getAttribute('combine'),
),
);
} elseif ($node instanceof \Twig_Node_Expression_Function) {
$name = version_compare(\Twig_Environment::VERSION, '1.2.0-DEV', '<')
? $node->getNode('name')->getAttribute('name')
: $node->getAttribute('name');
if ($this->twig->getFunction($name) instanceof AsseticFilterFunction) {
$arguments = array();
foreach ($node->getNode('arguments') as $argument) {
$arguments[] = eval('return '.$this->twig->compile($argument).';');
}
$invoker = $this->twig->getExtension('assetic')->getFilterInvoker($name);
$inputs = isset($arguments[0]) ? (array) $arguments[0] : array();
$filters = $invoker->getFilters();
$options = array_replace($invoker->getOptions(), isset($arguments[1]) ? $arguments[1] : array());
if (!isset($options['name'])) {
$options['name'] = $invoker->getFactory()->generateAssetName($inputs, $filters, $options);
}
$formulae[$options['name']] = array($inputs, $filters, $options);
}
}
foreach ($node as $child) {
if ($child instanceof \Twig_Node) {
$formulae += $this->loadNode($child);
}
}
return $formulae;
}
}
PK c]GM ÷ì^ ^ 3 src/Assetic/Extension/Twig/AsseticFilterInvoker.phpnu W+A„¶
*/
class AsseticFilterInvoker
{
private $factory;
private $filters;
private $options;
public function __construct($factory, $filter)
{
$this->factory = $factory;
if (is_array($filter) && isset($filter['filter'])) {
$this->filters = (array) $filter['filter'];
$this->options = isset($filter['options']) ? (array) $filter['options'] : array();
} else {
$this->filters = (array) $filter;
$this->options = array();
}
}
public function getFactory()
{
return $this->factory;
}
public function getFilters()
{
return $this->filters;
}
public function getOptions()
{
return $this->options;
}
public function invoke($input, array $options = array())
{
$asset = $this->factory->createAsset($input, $this->filters, $options + $this->options);
return $asset->getTargetPath();
}
}
PK c]GMH˜Qˆ
# src/Assetic/Util/ProcessBuilder.phpnu W+A„¶
*/
class ProcessBuilder
{
private $arguments;
private $cwd;
private $env;
private $stdin;
private $timeout = 60;
private $options = array();
private $inheritEnv = false;
public function __construct(array $arguments = array())
{
$this->arguments = $arguments;
}
/**
* Adds an unescaped argument to the command string.
*
* @param string $argument A command argument
*/
public function add($argument)
{
$this->arguments[] = $argument;
return $this;
}
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
return $this;
}
public function inheritEnvironmentVariables($inheritEnv = true)
{
$this->inheritEnv = $inheritEnv;
return $this;
}
public function setEnv($name, $value)
{
if (null === $this->env) {
$this->env = array();
}
$this->env[$name] = $value;
return $this;
}
public function setInput($stdin)
{
$this->stdin = $stdin;
return $this;
}
public function setTimeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
public function setOption($name, $value)
{
$this->options[$name] = $value;
return $this;
}
public function getProcess()
{
if (!count($this->arguments)) {
throw new \LogicException('You must add() command arguments before calling getProcess().');
}
$options = $this->options;
if (defined('PHP_WINDOWS_MAJOR_VERSION')) {
$options += array('bypass_shell' => true);
$args = $this->arguments;
$cmd = array_shift($args);
$script = '"'.$cmd.'"';
if ($args) {
$script .= ' '.implode(' ', array_map('escapeshellarg', $parts));
}
} else {
$script = implode(' ', array_map('escapeshellarg', $this->arguments));
}
$env = $this->inheritEnv && $_ENV ? ($this->env ?: array()) + $_ENV : $this->env;
return new Process($script, $this->cwd, $env, $this->stdin, $this->timeout, $options);
}
}
PK c]GMYI’}Ó' Ó' src/Assetic/Util/Process.phpnu W+A„¶
*
* @api
*/
class Process
{
private $commandline;
private $cwd;
private $env;
private $stdin;
private $timeout;
private $options;
private $exitcode;
private $status;
private $stdout;
private $stderr;
/**
* Constructor.
*
* @param string $commandline The command line to run
* @param string $cwd The working directory
* @param array $env The environment variables
* @param string $stdin The STDIN content
* @param integer $timeout The timeout in seconds
* @param array $options An array of options for proc_open
*
* @throws \RuntimeException When proc_open is not installed
*
* @api
*/
public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array())
{
if (!function_exists('proc_open')) {
throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
}
$this->commandline = $commandline;
$this->cwd = null === $cwd ? getcwd() : $cwd;
if (null !== $env) {
$this->env = array();
foreach ($env as $key => $value) {
$this->env[(binary) $key] = (binary) $value;
}
} else {
$this->env = null;
}
$this->stdin = $stdin;
$this->timeout = $timeout;
$this->options = array_merge(array('suppress_errors' => true, 'binary_pipes' => true, 'bypass_shell' => false), $options);
}
/**
* Runs the process.
*
* The callback receives the type of output (out or err) and
* some bytes from the output in real-time. It allows to have feedback
* from the independent process during execution.
*
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput() and getErrorOutput() methods.
*
* @param Closure|string|array $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return integer The exit status code
*
* @throws \RuntimeException When process can't be launch or is stopped
*
* @api
*/
public function run($callback = null)
{
$this->stdout = '';
$this->stderr = '';
$that = $this;
$callback = function ($type, $data) use ($that, $callback)
{
if ('out' == $type) {
$that->addOutput($data);
} else {
$that->addErrorOutput($data);
}
if (null !== $callback) {
call_user_func($callback, $type, $data);
}
};
$descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
$process = proc_open($this->commandline, $descriptors, $pipes, $this->cwd, $this->env, $this->options);
if (!is_resource($process)) {
throw new \RuntimeException('Unable to launch a new process.');
}
foreach ($pipes as $pipe) {
stream_set_blocking($pipe, false);
}
if (null === $this->stdin) {
fclose($pipes[0]);
$writePipes = null;
} else {
$writePipes = array($pipes[0]);
$stdinLen = strlen($this->stdin);
$stdinOffset = 0;
}
unset($pipes[0]);
while ($pipes || $writePipes) {
$r = $pipes;
$w = $writePipes;
$e = null;
$n = @stream_select($r, $w, $e, $this->timeout);
if (false === $n) {
break;
} elseif ($n === 0) {
proc_terminate($process);
throw new \RuntimeException('The process timed out.');
}
if ($w) {
$written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
if (false !== $written) {
$stdinOffset += $written;
}
if ($stdinOffset >= $stdinLen) {
fclose($writePipes[0]);
$writePipes = null;
}
}
foreach ($r as $pipe) {
$type = array_search($pipe, $pipes);
$data = fread($pipe, 8192);
if (strlen($data) > 0) {
call_user_func($callback, $type == 1 ? 'out' : 'err', $data);
}
if (false === $data || feof($pipe)) {
fclose($pipe);
unset($pipes[$type]);
}
}
}
$this->status = proc_get_status($process);
$time = 0;
while (1 == $this->status['running'] && $time < 1000000) {
$time += 1000;
usleep(1000);
$this->status = proc_get_status($process);
}
$exitcode = proc_close($process);
if ($this->status['signaled']) {
throw new \RuntimeException(sprintf('The process stopped because of a "%s" signal.', $this->status['stopsig']));
}
return $this->exitcode = $this->status['running'] ? $exitcode : $this->status['exitcode'];
}
/**
* Returns the output of the process (STDOUT).
*
* This only returns the output if you have not supplied a callback
* to the run() method.
*
* @return string The process output
*
* @api
*/
public function getOutput()
{
return $this->stdout;
}
/**
* Returns the error output of the process (STDERR).
*
* This only returns the error output if you have not supplied a callback
* to the run() method.
*
* @return string The process error output
*
* @api
*/
public function getErrorOutput()
{
return $this->stderr;
}
/**
* Returns the exit code returned by the process.
*
* @return integer The exit status code
*
* @api
*/
public function getExitCode()
{
return $this->exitcode;
}
/**
* Checks if the process ended successfully.
*
* @return Boolean true if the process ended successfully, false otherwise
*
* @api
*/
public function isSuccessful()
{
return 0 == $this->exitcode;
}
/**
* Returns true if the child process has been terminated by an uncaught signal.
*
* It always returns false on Windows.
*
* @return Boolean
*
* @api
*/
public function hasBeenSignaled()
{
return $this->status['signaled'];
}
/**
* Returns the number of the signal that caused the child process to terminate its execution.
*
* It is only meaningful if hasBeenSignaled() returns true.
*
* @return integer
*
* @api
*/
public function getTermSignal()
{
return $this->status['termsig'];
}
/**
* Returns true if the child process has been stopped by a signal.
*
* It always returns false on Windows.
*
* @return Boolean
*
* @api
*/
public function hasBeenStopped()
{
return $this->status['stopped'];
}
/**
* Returns the number of the signal that caused the child process to stop its execution
*
* It is only meaningful if hasBeenStopped() returns true.
*
* @return integer
*
* @api
*/
public function getStopSignal()
{
return $this->status['stopsig'];
}
public function addOutput($line)
{
$this->stdout .= $line;
}
public function addErrorOutput($line)
{
$this->stderr .= $line;
}
public function getCommandLine()
{
return $this->commandline;
}
public function setCommandLine($commandline)
{
$this->commandline = $commandline;
}
public function getTimeout()
{
return $this->timeout;
}
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
public function getWorkingDirectory()
{
return $this->cwd;
}
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
}
public function getEnv()
{
return $this->env;
}
public function setEnv(array $env)
{
$this->env = $env;
}
public function getStdin()
{
return $this->stdin;
}
public function setStdin($stdin)
{
$this->stdin = $stdin;
}
public function getOptions()
{
return $this->options;
}
public function setOptions(array $options)
{
$this->options = $options;
}
}
PK c]GM2à¥n n &