[
  {
    "path": ".gitignore",
    "content": "composer.lock\ncomposer.phar\nphpunit.xml\nvendor/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nphp:\n    - 5.3\n    - 5.4\n    - 5.5\n\nbefore_script:\n    - wget http://getcomposer.org/composer.phar\n    - php composer.phar install --dev\n\nscript: phpunit --coverage-text\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 0.3 (May 18, 2015)\n\n * Changed ProcessManager constructor to accept new Factory class as second\n   argument\n * Use shared memory for interprocess communications (@MattJaniszewski)\n * Added progress callbacks to Deferred\n * Added serializable objects for exit and error messages\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2013 OpenSky Project Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://secure.travis-ci.org/kriswallsmith/spork.png?branch=master)](http://travis-ci.org/kriswallsmith/spork)\n\nSpork: PHP on a Fork\n--------------------\n\n```php\n<?php\n\n$manager = new Spork\\ProcessManager();\n$manager->fork(function() {\n    // do something in another process!\n    return 'Hello from '.getmypid();\n})->then(function(Spork\\Fork $fork) {\n    // do something in the parent process when it's done!\n    echo \"{$fork->getPid()} says '{$fork->getResult()}'\\n\";\n});\n```\n\n### Example: Upload images to your CDN\n\nFeed an iterator into the process manager and it will break the job into\nmultiple batches and spread them across many processes.\n\n```php\n<?php\n\n$files = new RecursiveDirectoryIterator('/path/to/images');\n$files = new RecursiveIteratorIterator($files);\n\n$manager->process($files, function(SplFileInfo $file) {\n    // upload this file\n});\n```\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"kriswallsmith/spork\",\n    \"description\": \"Asynchronous PHP\",\n    \"homepage\": \"https://github.com/kriswallsmith/spork\",\n    \"type\": \"library\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Kris Wallsmith\",\n            \"email\": \"kris.wallsmith@gmail.com\",\n            \"homepage\": \"http://kriswallsmith.net/\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=5.3.0\",\n        \"ext-pcntl\": \"*\",\n        \"ext-posix\": \"*\",\n        \"ext-shmop\": \"*\",\n        \"symfony/event-dispatcher\": \"*\"\n    },\n    \"autoload\": {\n        \"psr-0\": { \"Spork\": \"src/\" }\n    }\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit bootstrap=\"./tests/bootstrap.php\" colors=\"true\">\n    <testsuites>\n        <testsuite name=\"Spork Test Suite\">\n            <directory suffix=\"Test.php\">./tests/Spork/Test/</directory>\n        </testsuite>\n    </testsuites>\n\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">./src/Spork/</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "src/Spork/Batch/BatchJob.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch;\n\nuse Spork\\Batch\\Strategy\\ChunkStrategy;\nuse Spork\\Batch\\Strategy\\StrategyInterface;\nuse Spork\\Exception\\UnexpectedTypeException;\nuse Spork\\ProcessManager;\n\nclass BatchJob\n{\n    private $manager;\n    private $data;\n    private $strategy;\n    private $name;\n    private $callback;\n\n    public function __construct(ProcessManager $manager, $data = null, StrategyInterface $strategy = null)\n    {\n        $this->manager = $manager;\n        $this->data = $data;\n        $this->strategy = $strategy ?: new ChunkStrategy();\n        $this->name = '<anonymous>';\n    }\n\n    public function setName($name)\n    {\n        $this->name = $name;\n\n        return $this;\n    }\n\n    public function setStrategy(StrategyInterface $strategy)\n    {\n        $this->strategy = $strategy;\n\n        return $this;\n    }\n\n    public function setData($data)\n    {\n        $this->data = $data;\n\n        return $this;\n    }\n\n    public function setCallback($callback)\n    {\n        if (!is_callable($callback)) {\n            throw new UnexpectedTypeException($callback, 'callable');\n        }\n\n        $this->callback = $callback;\n\n        return $this;\n    }\n\n    public function execute($callback = null)\n    {\n        if (null !== $callback) {\n            $this->setCallback($callback);\n        }\n\n        return $this->manager->fork($this)->setName($this->name.' batch');\n    }\n\n    /**\n     * Runs in a child process.\n     *\n     * @see execute()\n     */\n    public function __invoke()\n    {\n        $forks = array();\n        foreach ($this->strategy->createBatches($this->data) as $index => $batch) {\n            $forks[] = $this->manager\n                ->fork($this->strategy->createRunner($batch, $this->callback))\n                ->setName(sprintf('%s batch #%d', $this->name, $index))\n            ;\n        }\n\n        // block until all forks have exited\n        $this->manager->wait();\n\n        $results = array();\n        foreach ($forks as $fork) {\n            $results = array_merge($results, $fork->getResult());\n        }\n\n        return $results;\n    }\n}\n"
  },
  {
    "path": "src/Spork/Batch/BatchRunner.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch;\n\nuse Spork\\Exception\\UnexpectedTypeException;\nuse Spork\\SharedMemory;\n\nclass BatchRunner\n{\n    private $batch;\n    private $callback;\n\n    /**\n     * Constructor.\n     *\n     * The callback should be a callable with the following signature:\n     *\n     *     function($item, $index, $batch, $sharedMem)\n     *\n     * @param mixed    $batch    The batch\n     * @param callable $callback The callback\n     */\n    public function __construct($batch, $callback)\n    {\n        if (!is_callable($callback)) {\n            throw new UnexpectedTypeException($callback, 'callable');\n        }\n\n        $this->batch = $batch;\n        $this->callback = $callback;\n    }\n\n    public function __invoke(SharedMemory $shm)\n    {\n        // lazy batch...\n        if ($this->batch instanceof \\Closure) {\n            $this->batch = call_user_func($this->batch);\n        }\n\n        $results = array();\n        foreach ($this->batch as $index => $item) {\n            $results[$index] = call_user_func($this->callback, $item, $index, $this->batch, $shm);\n        }\n\n        return $results;\n    }\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/AbstractStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\nuse Spork\\Batch\\BatchRunner;\n\nabstract class AbstractStrategy implements StrategyInterface\n{\n    public function createRunner($batch, $callback)\n    {\n        return new BatchRunner($batch, $callback);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/CallbackStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\nuse Spork\\Exception\\UnexpectedTypeException;\n\nclass CallbackStrategy extends AbstractStrategy\n{\n    private $callback;\n\n    public function __construct($callback)\n    {\n        if (!is_callable($callback)) {\n            throw new UnexpectedTypeException($callback, 'callable');\n        }\n\n        $this->callback = $callback;\n    }\n\n    public function createBatches($data)\n    {\n        return call_user_func($this->callback, $data);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/ChunkStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\nuse Spork\\Exception\\UnexpectedTypeException;\n\n/**\n * Creates the batch iterator using array_chunk().\n */\nclass ChunkStrategy extends AbstractStrategy\n{\n    private $forks;\n    private $preserveKeys;\n\n    public function __construct($forks = 3, $preserveKeys = false)\n    {\n        $this->forks = $forks;\n        $this->preserveKeys = $preserveKeys;\n    }\n\n    public function createBatches($data)\n    {\n        if (!is_array($data) && !$data instanceof \\Traversable) {\n            throw new UnexpectedTypeException($data, 'array or Traversable');\n        }\n\n        if ($data instanceof \\Traversable) {\n            $data = iterator_to_array($data);\n        }\n\n        $size = ceil(count($data) / $this->forks);\n\n        return array_chunk($data, $size, $this->preserveKeys);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/DoctrineMongoStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\nclass DoctrineMongoStrategy extends MongoStrategy\n{\n    const DATA_CLASS = 'Doctrine\\MongoDB\\Cursor';\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/MongoStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\nuse Spork\\Exception\\UnexpectedTypeException;\n\n/**\n * Processes a Mongo cursor.\n *\n * If you use this strategy you MUST close your Mongo connection on the\n * spork.pre_fork event.\n *\n *     $mongo = new Mongo();\n *     $manager->addListener(Events::PRE_FORK, array($mongo, 'close'));\n */\nclass MongoStrategy extends AbstractStrategy\n{\n    const DATA_CLASS = 'MongoCursor';\n\n    private $size;\n    private $skip;\n\n    /**\n     * Constructor.\n     *\n     * @param integer $size The number of batches to create\n     * @param integer $skip The number of documents to skip\n     */\n    public function __construct($size = 3, $skip = 0)\n    {\n        $this->size = $size;\n        $this->skip = $skip;\n    }\n\n    public function createBatches($cursor)\n    {\n        $expected = static::DATA_CLASS;\n        if (!$cursor instanceof $expected) {\n            throw new UnexpectedTypeException($cursor, $expected);\n        }\n\n        $skip  = $this->skip;\n        $limit = ceil(($cursor->count(true) - $skip) / $this->size);\n\n        $batches = array();\n        for ($i = 0; $i < $this->size; $i++) {\n            $batches[] = function() use($cursor, $skip, $i, $limit) {\n                return $cursor->skip($skip + $i * $limit)->limit($limit);\n            };\n        }\n\n        return $batches;\n    }\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/StrategyInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\n/**\n * @see BatchJob::__invoke()\n */\ninterface StrategyInterface\n{\n    /**\n     * Creates an iterator for the supplied data.\n     *\n     * @param mixed $data The raw batch data\n     *\n     * @return array|\\Traversable An iterator of batches\n     */\n    function createBatches($data);\n\n    /**\n     * Creates a batch runner for the supplied list.\n     *\n     * A batch runner is a callable that is passed to ProcessManager::fork()\n     * that should run each item in the supplied batch through a callable.\n     *\n     * @param mixed    $batch    A batch of items\n     * @param callable $callback The batch callback\n     *\n     * @return callable A callable for the child process\n     */\n    function createRunner($batch, $callback);\n}\n"
  },
  {
    "path": "src/Spork/Batch/Strategy/ThrottleStrategy.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Batch\\Strategy;\n\nuse Spork\\Util\\ThrottleIterator;\n\nclass ThrottleStrategy implements StrategyInterface\n{\n    private $delegate;\n    private $threshold;\n\n    public function __construct(StrategyInterface $delegate, $threshold = 3)\n    {\n        $this->delegate = $delegate;\n        $this->threshold = $threshold;\n    }\n\n    public function createBatches($data)\n    {\n        $batches = $this->delegate->createBatches($data);\n\n        // wrap each batch in the throttle iterator\n        foreach ($batches as $i => $batch) {\n            $batches[$i] = new ThrottleIterator($batch, $this->threshold);\n        }\n\n        return $batches;\n    }\n\n    public function createRunner($batch, $callback)\n    {\n        return $this->delegate->createRunner($batch, $callback);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Deferred/Deferred.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Deferred;\n\nuse Spork\\Exception\\UnexpectedTypeException;\n\nclass Deferred implements DeferredInterface\n{\n    private $state;\n    private $progressCallbacks;\n    private $alwaysCallbacks;\n    private $doneCallbacks;\n    private $failCallbacks;\n    private $callbackArgs;\n\n    public function __construct()\n    {\n        $this->state = DeferredInterface::STATE_PENDING;\n\n        $this->progressCallbacks = array();\n        $this->alwaysCallbacks = array();\n        $this->doneCallbacks = array();\n        $this->failCallbacks = array();\n    }\n\n    public function getState()\n    {\n        return $this->state;\n    }\n\n    public function progress($progress)\n    {\n        if (!is_callable($progress)) {\n            throw new UnexpectedTypeException($progress, 'callable');\n        }\n\n        $this->progressCallbacks[] = $progress;\n\n        return $this;\n    }\n\n    public function always($always)\n    {\n        if (!is_callable($always)) {\n            throw new UnexpectedTypeException($always, 'callable');\n        }\n\n        switch ($this->state) {\n            case DeferredInterface::STATE_PENDING:\n                $this->alwaysCallbacks[] = $always;\n                break;\n            default:\n                call_user_func_array($always, $this->callbackArgs);\n                break;\n        }\n\n        return $this;\n    }\n\n    public function done($done)\n    {\n        if (!is_callable($done)) {\n            throw new UnexpectedTypeException($done, 'callable');\n        }\n\n        switch ($this->state) {\n            case DeferredInterface::STATE_PENDING:\n                $this->doneCallbacks[] = $done;\n                break;\n            case DeferredInterface::STATE_RESOLVED:\n                call_user_func_array($done, $this->callbackArgs);\n        }\n\n        return $this;\n    }\n\n    public function fail($fail)\n    {\n        if (!is_callable($fail)) {\n            throw new UnexpectedTypeException($fail, 'callable');\n        }\n\n        switch ($this->state) {\n            case DeferredInterface::STATE_PENDING:\n                $this->failCallbacks[] = $fail;\n                break;\n            case DeferredInterface::STATE_REJECTED:\n                call_user_func_array($fail, $this->callbackArgs);\n                break;\n        }\n\n        return $this;\n    }\n\n    public function then($done, $fail = null)\n    {\n        $this->done($done);\n\n        if ($fail) {\n            $this->fail($fail);\n        }\n\n        return $this;\n    }\n\n    public function notify()\n    {\n        if (DeferredInterface::STATE_PENDING !== $this->state) {\n            throw new \\LogicException('Cannot notify a deferred object that is no longer pending');\n        }\n\n        $args = func_get_args();\n        foreach ($this->progressCallbacks as $func) {\n            call_user_func_array($func, $args);\n        }\n\n        return $this;\n    }\n\n    public function resolve()\n    {\n        if (DeferredInterface::STATE_REJECTED === $this->state) {\n            throw new \\LogicException('Cannot resolve a deferred object that has already been rejected');\n        }\n\n        if (DeferredInterface::STATE_RESOLVED === $this->state) {\n            return $this;\n        }\n\n        $this->state = DeferredInterface::STATE_RESOLVED;\n        $this->callbackArgs = func_get_args();\n\n        while ($func = array_shift($this->alwaysCallbacks)) {\n            call_user_func_array($func, $this->callbackArgs);\n        }\n\n        while ($func = array_shift($this->doneCallbacks)) {\n            call_user_func_array($func, $this->callbackArgs);\n        }\n\n        return $this;\n    }\n\n    public function reject()\n    {\n        if (DeferredInterface::STATE_RESOLVED === $this->state) {\n            throw new \\LogicException('Cannot reject a deferred object that has already been resolved');\n        }\n\n        if (DeferredInterface::STATE_REJECTED === $this->state) {\n            return $this;\n        }\n\n        $this->state = DeferredInterface::STATE_REJECTED;\n        $this->callbackArgs = func_get_args();\n\n        while ($func = array_shift($this->alwaysCallbacks)) {\n            call_user_func_array($func, $this->callbackArgs);\n        }\n\n        while ($func = array_shift($this->failCallbacks)) {\n            call_user_func_array($func, $this->callbackArgs);\n        }\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Spork/Deferred/DeferredAggregate.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Deferred;\n\nuse Spork\\Exception\\UnexpectedTypeException;\n\nclass DeferredAggregate implements PromiseInterface\n{\n    private $children;\n    private $delegate;\n\n    public function __construct(array $children)\n    {\n        // validate children\n        foreach ($children as $child) {\n            if (!$child instanceof PromiseInterface) {\n                throw new UnexpectedTypeException($child, 'Spork\\Deferred\\PromiseInterface');\n            }\n        }\n\n        $this->children = $children;\n        $this->delegate = new Deferred();\n\n        // connect to each child\n        foreach ($this->children as $child) {\n            $child->always(array($this, 'tick'));\n        }\n\n        // always tick once now\n        $this->tick();\n    }\n\n    public function getState()\n    {\n        return $this->delegate->getState();\n    }\n\n    public function getChildren()\n    {\n        return $this->children;\n    }\n\n    public function progress($progress)\n    {\n        $this->delegate->progress($progress);\n\n        return $this;\n    }\n\n    public function always($always)\n    {\n        $this->delegate->always($always);\n\n        return $this;\n    }\n\n    public function done($done)\n    {\n        $this->delegate->done($done);\n\n        return $this;\n    }\n\n    public function fail($fail)\n    {\n        $this->delegate->fail($fail);\n\n        return $this;\n    }\n\n    public function then($done, $fail = null)\n    {\n        $this->delegate->then($done, $fail);\n\n        return $this;\n    }\n\n    public function tick()\n    {\n        $pending = count($this->children);\n\n        foreach ($this->children as $child) {\n            switch ($child->getState()) {\n                case PromiseInterface::STATE_REJECTED:\n                    $this->delegate->reject($this);\n\n                    return;\n                case PromiseInterface::STATE_RESOLVED:\n                    --$pending;\n                    break;\n            }\n        }\n\n        if (!$pending) {\n            $this->delegate->resolve($this);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Spork/Deferred/DeferredInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Deferred;\n\ninterface DeferredInterface extends PromiseInterface\n{\n    /**\n     * Notifies the promise of progress.\n     *\n     * @param mixed $args Any arguments will be passed along to the callbacks\n     *\n     * @return DeferredInterface The current promise\n     * @throws \\LogicException   If the promise is not pending\n     */\n    function notify();\n\n    /**\n     * Marks the current promise as successful.\n     *\n     * Calls \"always\" callbacks first, followed by \"done\" callbacks.\n     *\n     * @param mixed $args Any arguments will be passed along to the callbacks\n     *\n     * @return DeferredInterface The current promise\n     * @throws \\LogicException   If the promise was previously rejected\n     */\n    function resolve();\n\n    /**\n     * Marks the current promise as failed.\n     *\n     * Calls \"always\" callbacks first, followed by \"fail\" callbacks.\n     *\n     * @param mixed $args Any arguments will be passed along to the callbacks\n     *\n     * @return DeferredInterface The current promise\n     * @throws \\LogicException   If the promise was previously resolved\n     */\n    function reject();\n}\n"
  },
  {
    "path": "src/Spork/Deferred/PromiseInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Deferred;\n\ninterface PromiseInterface\n{\n    const STATE_PENDING  = 'pending';\n    const STATE_RESOLVED = 'resolved';\n    const STATE_REJECTED = 'rejected';\n\n    /**\n     * Returns the promise state.\n     *\n     *  * PromiseInterface::STATE_PENDING:  The promise is still open\n     *  * PromiseInterface::STATE_RESOLVED: The promise completed successfully\n     *  * PromiseInterface::STATE_REJECTED: The promise failed\n     *\n     * @return string A promise state constant\n     */\n    function getState();\n\n    /**\n     * Adds a callback to be called upon progress.\n     *\n     * @param callable $progress The callback\n     *\n     * @return PromiseInterface The current promise\n     */\n    function progress($progress);\n\n    /**\n     * Adds a callback to be called whether the promise is resolved or rejected.\n     *\n     * The callback will be called immediately if the promise is no longer\n     * pending.\n     *\n     * @param callable $always The callback\n     *\n     * @return PromiseInterface The current promise\n     */\n    function always($always);\n\n    /**\n     * Adds a callback to be called when the promise completes successfully.\n     *\n     * The callback will be called immediately if the promise state is resolved.\n     *\n     * @param callable $done The callback\n     *\n     * @return PromiseInterface The current promise\n     */\n    function done($done);\n\n    /**\n     * Adds a callback to be called when the promise fails.\n     *\n     * The callback will be called immediately if the promise state is rejected.\n     *\n     * @param callable $done The callback\n     *\n     * @return PromiseInterface The current promise\n     */\n    function fail($fail);\n\n    /**\n     * Adds done and fail callbacks.\n     *\n     * @param callable $done The done callback\n     * @param callable $fail The fail callback\n     *\n     * @return PromiseInterface The current promise\n     */\n    function then($done, $fail = null);\n}\n"
  },
  {
    "path": "src/Spork/EventDispatcher/EventDispatcher.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\ndeclare(ticks=1);\n\nnamespace Spork\\EventDispatcher;\n\nuse Symfony\\Component\\EventDispatcher\\EventDispatcher as BaseEventDispatcher;\n\n/**\n * Adds support for listening to signals.\n */\nclass EventDispatcher extends BaseEventDispatcher implements EventDispatcherInterface\n{\n    public function dispatchSignal($signal)\n    {\n        $this->dispatch('spork.signal.'.$signal);\n    }\n\n    public function addSignalListener($signal, $callable, $priority = 0)\n    {\n        $this->addListener('spork.signal.'.$signal, $callable, $priority);\n        pcntl_signal($signal, array($this, 'dispatchSignal'));\n    }\n\n    public function removeSignalListener($signal, $callable)\n    {\n        $this->removeListener('spork.signal.'.$signal, $callable);\n    }\n}\n"
  },
  {
    "path": "src/Spork/EventDispatcher/EventDispatcherInterface.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\EventDispatcher;\n\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface as BaseEventDispatcherInterface;\n\ninterface EventDispatcherInterface extends BaseEventDispatcherInterface\n{\n    function dispatchSignal($signal);\n    function addSignalListener($signal, $callable, $priority = 0);\n    function removeSignalListener($signal, $callable);\n}\n"
  },
  {
    "path": "src/Spork/EventDispatcher/Events.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\EventDispatcher;\n\nfinal class Events\n{\n    /**\n     * Dispatched in the parent process before forking.\n     */\n    const PRE_FORK = 'spork.pre_fork';\n\n    /**\n     * Notifies in the child process after forking.\n     */\n    const POST_FORK = 'spork.post_fork';\n}\n"
  },
  {
    "path": "src/Spork/EventDispatcher/WrappedEventDispatcher.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\ndeclare(ticks=1);\n\nnamespace Spork\\EventDispatcher;\n\nuse Symfony\\Component\\EventDispatcher\\Event;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcherInterface as BaseEventDispatcherInterface;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass WrappedEventDispatcher implements EventDispatcherInterface\n{\n    private $delegate;\n\n    public function __construct(BaseEventDispatcherInterface $delegate)\n    {\n        $this->delegate = $delegate;\n    }\n\n    public function dispatchSignal($signal)\n    {\n        $this->delegate->dispatch('spork.signal.'.$signal);\n    }\n\n    public function addSignalListener($signal, $callable, $priority = 0)\n    {\n        $this->delegate->addListener('spork.signal.'.$signal, $callable, $priority);\n        pcntl_signal($signal, array($this, 'dispatchSignal'));\n    }\n\n    public function removeSignalListener($signal, $callable)\n    {\n        $this->delegate->removeListener('spork.signal.'.$signal, $callable);\n    }\n\n    public function dispatch($eventName, Event $event = null)\n    {\n        return $this->delegate->dispatch($eventName, $event);\n    }\n\n    public function addListener($eventName, $listener, $priority = 0)\n    {\n        $this->delegate->addListener($eventName, $listener, $priority);\n    }\n\n    public function addSubscriber(EventSubscriberInterface $subscriber)\n    {\n        $this->delegate->addSubscriber($subscriber);\n    }\n\n    public function removeListener($eventName, $listener)\n    {\n        $this->delegate->removeListener($eventName, $listener);\n    }\n\n    public function removeSubscriber(EventSubscriberInterface $subscriber)\n    {\n        $this->delegate->removeSubscriber($subscriber);\n    }\n\n    public function getListeners($eventName = null)\n    {\n        return $this->delegate->getListeners($eventName);\n    }\n\n    public function hasListeners($eventName = null)\n    {\n        return $this->delegate->hasListeners($eventName);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Exception/ForkException.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Exception;\n\nuse Spork\\Util\\Error;\n\n/**\n * Turns an error passed through shared memory into an exception.\n */\nclass ForkException extends \\RuntimeException\n{\n    private $name;\n    private $pid;\n    private $error;\n\n    public function __construct($name, $pid, Error $error = null)\n    {\n        $this->name = $name;\n        $this->pid = $pid;\n        $this->error = $error;\n\n        if ($error) {\n            if (__CLASS__ === $error->getClass()) {\n                parent::__construct(sprintf('%s via \"%s\" fork (%d)', $error->getMessage(), $name, $pid));\n            } else {\n                parent::__construct(sprintf(\n                    '%s (%d) thrown in \"%s\" fork (%d): \"%s\" (%s:%d)',\n                    $error->getClass(),\n                    $error->getCode(),\n                    $name,\n                    $pid,\n                    $error->getMessage(),\n                    $error->getFile(),\n                    $error->getLine()\n                ));\n            }\n        } else {\n            parent::__construct(sprintf('An unknown error occurred in \"%s\" fork (%d)', $name, $pid));\n        }\n    }\n\n    public function getPid()\n    {\n        return $this->pid;\n    }\n\n    public function getError()\n    {\n        return $this->error;\n    }\n}\n"
  },
  {
    "path": "src/Spork/Exception/ProcessControlException.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Exception;\n\nclass ProcessControlException extends \\RuntimeException\n{\n}\n"
  },
  {
    "path": "src/Spork/Exception/UnexpectedTypeException.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Exception;\n\nclass UnexpectedTypeException extends \\LogicException\n{\n    public function __construct($value, $expectedType)\n    {\n        parent::__construct(sprintf('Expected argument of type \"%s\", \"%s\" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));\n    }\n}\n"
  },
  {
    "path": "src/Spork/Factory.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork;\n\nuse Spork\\Batch\\BatchJob;\nuse Spork\\Batch\\Strategy\\StrategyInterface;\n\nclass Factory\n{\n    /**\n     * Creates a new batch job instance.\n     *\n     * @param ProcessManager    $manager  The process manager\n     * @param null              $data     Data for the batch job\n     * @param StrategyInterface $strategy The strategy\n     *\n     * @return BatchJob A new batch job instance\n     */\n    public function createBatchJob(ProcessManager $manager, $data = null, StrategyInterface $strategy = null)\n    {\n        return new BatchJob($manager, $data, $strategy);\n    }\n\n    /**\n     * Creates a new shared memory instance.\n     *\n     * @param integer $pid    The child process id or null if this is the child\n     * @param integer $signal The signal to send after writing to shared memory\n     *\n     * @return SharedMemory A new shared memory instance\n     */\n    public function createSharedMemory($pid = null, $signal = null)\n    {\n        return new SharedMemory($pid, $signal);\n    }\n\n    /**\n     * Creates a new fork instance.\n     *\n     * @param int          $pid   Process id\n     * @param SharedMemory $shm   Shared memory\n     * @param bool         $debug Debug mode\n     *\n     * @return Fork A new fork instance\n     */\n    public function createFork($pid, SharedMemory $shm, $debug = false)\n    {\n        return new Fork($pid, $shm, $debug);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Fork.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork;\n\nuse Spork\\Deferred\\Deferred;\nuse Spork\\Deferred\\DeferredInterface;\nuse Spork\\Exception\\ForkException;\nuse Spork\\Exception\\ProcessControlException;\nuse Spork\\Util\\ExitMessage;\n\nclass Fork implements DeferredInterface\n{\n    private $defer;\n    private $pid;\n    private $shm;\n    private $debug;\n    private $name;\n    private $status;\n    private $message;\n\n    public function __construct($pid, SharedMemory $shm, $debug = false)\n    {\n        $this->defer = new Deferred();\n        $this->pid   = $pid;\n        $this->shm   = $shm;\n        $this->debug = $debug;\n        $this->name  = '<anonymous>';\n    }\n\n    /**\n     * Assign a name to the current fork (useful for debugging).\n     */\n    public function setName($name)\n    {\n        $this->name = $name;\n\n        return $this;\n    }\n\n    public function getPid()\n    {\n        return $this->pid;\n    }\n\n    public function wait($hang = true)\n    {\n        if ($this->isExited()) {\n            return $this;\n        }\n\n        if (-1 === $pid = pcntl_waitpid($this->pid, $status, ($hang ? 0 : WNOHANG) | WUNTRACED)) {\n            throw new ProcessControlException('Error while waiting for process '.$this->pid);\n        }\n\n        if ($this->pid === $pid) {\n            $this->processWaitStatus($status);\n        }\n\n        return $this;\n    }\n\n    /**\n     * Processes a status value retrieved while waiting for this fork to exit.\n     */\n    public function processWaitStatus($status)\n    {\n        if ($this->isExited()) {\n            throw new \\LogicException('Cannot set status on an exited fork');\n        }\n\n        $this->status = $status;\n\n        if ($this->isExited()) {\n            $this->receive();\n\n            $this->isSuccessful() ? $this->resolve() : $this->reject();\n\n            if ($this->debug && (!$this->isSuccessful() || $this->getError())) {\n                throw new ForkException($this->name, $this->pid, $this->getError());\n            }\n        }\n    }\n\n    public function receive()\n    {\n        $messages = array();\n\n        foreach ($this->shm->receive() as $message) {\n            if ($message instanceof ExitMessage) {\n                $this->message = $message;\n            } else {\n                $messages[] = $message;\n            }\n        }\n\n        return $messages;\n    }\n\n    public function kill($signal = SIGINT)\n    {\n        if (false === $this->shm->signal($signal)) {\n            throw new ProcessControlException('Unable to send signal');\n        }\n\n        return $this;\n    }\n\n    public function getResult()\n    {\n        if ($this->message) {\n            return $this->message->getResult();\n        }\n    }\n\n    public function getOutput()\n    {\n        if ($this->message) {\n            return $this->message->getOutput();\n        }\n    }\n\n    public function getError()\n    {\n        if ($this->message) {\n            return $this->message->getError();\n        }\n    }\n\n    public function isSuccessful()\n    {\n        return 0 === $this->getExitStatus();\n    }\n\n    public function isExited()\n    {\n        return null !== $this->status && pcntl_wifexited($this->status);\n    }\n\n    public function isStopped()\n    {\n        return null !== $this->status && pcntl_wifstopped($this->status);\n    }\n\n    public function isSignaled()\n    {\n        return null !== $this->status && pcntl_wifsignaled($this->status);\n    }\n\n    public function getExitStatus()\n    {\n        if (null !== $this->status) {\n            return pcntl_wexitstatus($this->status);\n        }\n    }\n\n    public function getTermSignal()\n    {\n        if (null !== $this->status) {\n            return pcntl_wtermsig($this->status);\n        }\n    }\n\n    public function getStopSignal()\n    {\n        if (null !== $this->status) {\n            return pcntl_wstopsig($this->status);\n        }\n    }\n\n    public function getState()\n    {\n        return $this->defer->getState();\n    }\n\n    public function progress($progress)\n    {\n        $this->defer->progress($progress);\n\n        return $this;\n    }\n\n    public function always($always)\n    {\n        $this->defer->always($always);\n\n        return $this;\n    }\n\n    public function done($done)\n    {\n        $this->defer->done($done);\n\n        return $this;\n    }\n\n    public function fail($fail)\n    {\n        $this->defer->fail($fail);\n\n        return $this;\n    }\n\n    public function then($done, $fail = null)\n    {\n        $this->defer->then($done, $fail);\n\n        return $this;\n    }\n\n    public function notify()\n    {\n        $args = func_get_args();\n        array_unshift($args, $this);\n\n        call_user_func_array(array($this->defer, 'notify'), $args);\n\n        return $this;\n    }\n\n    public function resolve()\n    {\n        $args = func_get_args();\n        array_unshift($args, $this);\n\n        call_user_func_array(array($this->defer, 'resolve'), $args);\n\n        return $this;\n    }\n\n    public function reject()\n    {\n        $args = func_get_args();\n        array_unshift($args, $this);\n\n        call_user_func_array(array($this->defer, 'reject'), $args);\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Spork/ProcessManager.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork;\n\nuse Spork\\Batch\\Strategy\\StrategyInterface;\nuse Spork\\EventDispatcher\\EventDispatcher;\nuse Spork\\EventDispatcher\\EventDispatcherInterface;\nuse Spork\\EventDispatcher\\Events;\nuse Spork\\Exception\\ProcessControlException;\nuse Spork\\Exception\\UnexpectedTypeException;\nuse Spork\\Util\\Error;\nuse Spork\\Util\\ExitMessage;\n\nclass ProcessManager\n{\n    private $dispatcher;\n    private $factory;\n    private $debug;\n    private $zombieOkay;\n    private $signal;\n\n    /** @var Fork[] */\n    private $forks;\n\n    public function __construct(EventDispatcherInterface $dispatcher = null, Factory $factory = null, $debug = false)\n    {\n        $this->dispatcher = $dispatcher ?: new EventDispatcher();\n        $this->factory = $factory ?: new Factory();\n        $this->debug = $debug;\n        $this->zombieOkay = false;\n        $this->forks = array();\n    }\n\n    public function __destruct()\n    {\n        if (!$this->zombieOkay) {\n            $this->wait();\n        }\n    }\n\n    public function getEventDispatcher()\n    {\n        return $this->dispatcher;\n    }\n\n    public function addListener($eventName, $listener, $priority = 0)\n    {\n        if (is_integer($eventName)) {\n            $this->dispatcher->addSignalListener($eventName, $listener, $priority);\n        } else {\n            $this->dispatcher->addListener($eventName, $listener, $priority);\n        }\n    }\n\n    public function setDebug($debug)\n    {\n        $this->debug = $debug;\n    }\n\n    public function zombieOkay($zombieOkay = true)\n    {\n        $this->zombieOkay = $zombieOkay;\n    }\n\n    public function createBatchJob($data = null, StrategyInterface $strategy = null)\n    {\n        return $this->factory->createBatchJob($this, $data, $strategy);\n    }\n\n    public function process($data, $callable, StrategyInterface $strategy = null)\n    {\n        return $this->createBatchJob($data, $strategy)->execute($callable);\n    }\n\n    /**\n     * Forks something into another process and returns a deferred object.\n     */\n    public function fork($callable)\n    {\n        if (!is_callable($callable)) {\n            throw new UnexpectedTypeException($callable, 'callable');\n        }\n\n        // allow the system to cleanup before forking\n        $this->dispatcher->dispatch(Events::PRE_FORK);\n\n        if (-1 === $pid = pcntl_fork()) {\n            throw new ProcessControlException('Unable to fork a new process');\n        }\n\n        if (0 === $pid) {\n            // reset the list of child processes\n            $this->forks = array();\n\n            // setup the shared memory\n            $shm = $this->factory->createSharedMemory(null, $this->signal);\n            $message = new ExitMessage();\n\n            // phone home on shutdown\n            register_shutdown_function(function() use($shm, $message) {\n                $status = null;\n\n                try {\n                    $shm->send($message, false);\n                } catch (\\Exception $e) {\n                    // probably an error serializing the result\n                    $message->setResult(null);\n                    $message->setError(Error::fromException($e));\n\n                    $shm->send($message, false);\n\n                    exit(2);\n                }\n            });\n\n            // dispatch an event so the system knows it's in a new process\n            $this->dispatcher->dispatch(Events::POST_FORK);\n\n            if (!$this->debug) {\n                ob_start();\n            }\n\n            try {\n                $result = call_user_func($callable, $shm);\n\n                $message->setResult($result);\n                $status = is_integer($result) ? $result : 0;\n            } catch (\\Exception $e) {\n                $message->setError(Error::fromException($e));\n                $status = 1;\n            }\n\n            if (!$this->debug) {\n                $message->setOutput(ob_get_clean());\n            }\n\n            exit($status);\n        }\n\n        // connect to shared memory\n        $shm = $this->factory->createSharedMemory($pid);\n\n        return $this->forks[$pid] = $this->factory->createFork($pid, $shm, $this->debug);\n    }\n\n    public function monitor($signal = SIGUSR1)\n    {\n        $this->signal = $signal;\n        $this->dispatcher->addSignalListener($signal, array($this, 'check'));\n    }\n\n    public function check()\n    {\n        foreach ($this->forks as $fork) {\n            foreach ($fork->receive() as $message) {\n                $fork->notify($message);\n            }\n        }\n    }\n\n    public function wait($hang = true)\n    {\n        foreach ($this->forks as $fork) {\n            $fork->wait($hang);\n        }\n    }\n\n    public function waitForNext($hang = true)\n    {\n        if (-1 === $pid = pcntl_wait($status, ($hang ? WNOHANG : 0) | WUNTRACED)) {\n            throw new ProcessControlException('Error while waiting for next fork to exit');\n        }\n\n        if (isset($this->forks[$pid])) {\n            $this->forks[$pid]->processWaitStatus($status);\n\n            return $this->forks[$pid];\n        }\n    }\n\n    public function waitFor($pid, $hang = true)\n    {\n        if (!isset($this->forks[$pid])) {\n            throw new \\InvalidArgumentException('There is no fork with PID '.$pid);\n        }\n\n        return $this->forks[$pid]->wait($hang);\n    }\n\n    /**\n     * Sends a signal to all forks.\n     */\n    public function killAll($signal = SIGINT)\n    {\n        foreach ($this->forks as $fork) {\n            $fork->kill($signal);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Spork/SharedMemory.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork;\n\nuse Spork\\Exception\\ProcessControlException;\n\n/**\n * Sends messages between processes.\n */\nclass SharedMemory\n{\n    private $pid;\n    private $ppid;\n    private $signal;\n\n    /**\n     * Constructor.\n     *\n     * @param integer $pid    The child process id or null if this is the child\n     * @param integer $signal The signal to send after writing to shared memory\n     */\n    public function __construct($pid = null, $signal = null)\n    {\n        if (null === $pid) {\n            // child\n            $pid   = posix_getpid();\n            $ppid  = posix_getppid();\n        } else {\n            // parent\n            $ppid  = null;\n        }\n\n        $this->pid  = $pid;\n        $this->ppid = $ppid;\n        $this->signal = $signal;\n    }\n\n    /**\n     * Reads all messages from shared memory.\n     *\n     * @return array An array of messages\n     */\n    public function receive()\n    {\n        if (($shmId = @shmop_open($this->pid, 'a', 0, 0)) > 0) {\n            $serializedMessages = shmop_read($shmId, 0, shmop_size($shmId));\n            shmop_delete($shmId);\n            shmop_close($shmId);\n\n            return unserialize($serializedMessages);\n        }\n\n        return array();\n    }\n\n    /**\n     * Writes a message to the shared memory.\n     *\n     * @param mixed   $message The message to send\n     * @param integer $signal  The signal to send afterward\n     * @param integer $pause   The number of microseconds to pause after signalling\n     */\n    public function send($message, $signal = null, $pause = 500)\n    {\n        $messageArray = array();\n\n        if (($shmId = @shmop_open($this->pid, 'a', 0, 0)) > 0) {\n            // Read any existing messages in shared memory\n            $readMessage = shmop_read($shmId, 0, shmop_size($shmId));\n            $messageArray[] = unserialize($readMessage);\n            shmop_delete($shmId);\n            shmop_close($shmId);\n        }\n\n        // Add the current message to the end of the array, and serialize it\n        $messageArray[] = $message;\n        $serializedMessage = serialize($messageArray);\n\n        // Write new serialized message to shared memory\n        $shmId = shmop_open($this->pid, 'c', 0644, strlen($serializedMessage));\n        if (!$shmId) {\n            throw new ProcessControlException(sprintf('Not able to create shared memory segment for PID: %s', $this->pid));\n        } else if (shmop_write($shmId, $serializedMessage, 0) !== strlen($serializedMessage)) {\n            throw new ProcessControlException(\n                sprintf('Not able to write message to shared memory segment for segment ID: %s', $shmId)\n            );\n        }\n\n        if (false === $signal) {\n            return;\n        }\n\n        $this->signal($signal ?: $this->signal);\n        usleep($pause);\n    }\n\n    /**\n     * Sends a signal to the other process.\n     */\n    public function signal($signal)\n    {\n        $pid = null === $this->ppid ? $this->pid : $this->ppid;\n\n        return posix_kill($pid, $signal);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Util/Error.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Util;\n\nclass Error implements \\Serializable\n{\n    private $class;\n    private $message;\n    private $file;\n    private $line;\n    private $code;\n\n    public static function fromException(\\Exception $e)\n    {\n        $flat = new static();\n        $flat->setClass(get_class($e));\n        $flat->setMessage($e->getMessage());\n        $flat->setFile($e->getFile());\n        $flat->setLine($e->getLine());\n        $flat->setCode($e->getCode());\n\n        return $flat;\n    }\n\n    public function getClass()\n    {\n        return $this->class;\n    }\n\n    public function setClass($class)\n    {\n        $this->class = $class;\n    }\n\n    public function getMessage()\n    {\n        return $this->message;\n    }\n\n    public function setMessage($message)\n    {\n        $this->message = $message;\n    }\n\n    public function getFile()\n    {\n        return $this->file;\n    }\n\n    public function setFile($file)\n    {\n        $this->file = $file;\n    }\n\n    public function getLine()\n    {\n        return $this->line;\n    }\n\n    public function setLine($line)\n    {\n        $this->line = $line;\n    }\n\n    public function getCode()\n    {\n        return $this->code;\n    }\n\n    public function setCode($code)\n    {\n        $this->code = $code;\n    }\n\n    public function serialize()\n    {\n        return serialize(array(\n            $this->class,\n            $this->message,\n            $this->file,\n            $this->line,\n            $this->code,\n        ));\n    }\n\n    public function unserialize($str)\n    {\n        list(\n            $this->class,\n            $this->message,\n            $this->file,\n            $this->line,\n            $this->code\n        ) = unserialize($str);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Util/ExitMessage.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Util;\n\nclass ExitMessage implements \\Serializable\n{\n    private $result;\n    private $output;\n    private $error;\n\n    public function getResult()\n    {\n        return $this->result;\n    }\n\n    public function setResult($result)\n    {\n        $this->result = $result;\n    }\n\n    public function getOutput()\n    {\n        return $this->output;\n    }\n\n    public function setOutput($output)\n    {\n        $this->output = $output;\n    }\n\n    public function getError()\n    {\n        return $this->error;\n    }\n\n    public function setError(Error $error)\n    {\n        $this->error = $error;\n    }\n\n    public function serialize()\n    {\n        return serialize(array(\n            $this->result,\n            $this->output,\n            $this->error,\n        ));\n    }\n\n    public function unserialize($str)\n    {\n        list(\n            $this->result,\n            $this->output,\n            $this->error\n        ) = unserialize($str);\n    }\n}\n"
  },
  {
    "path": "src/Spork/Util/ThrottleIterator.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Util;\n\nuse Spork\\Exception\\UnexpectedTypeException;\n\n/**\n * Throttles iteration based on a system load threshold.\n */\nclass ThrottleIterator implements \\OuterIterator\n{\n    private $inner;\n    private $threshold;\n    private $lastThrottle;\n\n    public function __construct($inner, $threshold)\n    {\n        if (!is_callable($inner) && !is_array($inner) && !$inner instanceof \\Traversable) {\n            throw new UnexpectedTypeException($inner, 'callable, array, or Traversable');\n        }\n\n        $this->inner = $inner;\n        $this->threshold = $threshold;\n    }\n\n    /**\n     * Attempts to lazily resolve the supplied inner to an instance of Iterator.\n     */\n    public function getInnerIterator()\n    {\n        if (is_callable($this->inner)) {\n            // callable\n            $this->inner = call_user_func($this->inner);\n        }\n\n        if (is_array($this->inner)) {\n            // array\n            $this->inner = new \\ArrayIterator($this->inner);\n        } elseif ($this->inner instanceof \\IteratorAggregate) {\n            // IteratorAggregate\n            while ($this->inner instanceof \\IteratorAggregate) {\n                $this->inner = $this->inner->getIterator();\n            }\n        }\n\n        if (!$this->inner instanceof \\Iterator) {\n            throw new UnexpectedTypeException($this->inner, 'Iterator');\n        }\n\n        return $this->inner;\n    }\n\n    public function current()\n    {\n        // only throttle every 5s\n        if ($this->lastThrottle < time() - 5) {\n            $this->throttle();\n        }\n\n        return $this->getInnerIterator()->current();\n    }\n\n    public function key()\n    {\n        return $this->getInnerIterator()->key();\n    }\n\n    public function next()\n    {\n        return $this->getInnerIterator()->next();\n    }\n\n    public function rewind()\n    {\n        return $this->getInnerIterator()->rewind();\n    }\n\n    public function valid()\n    {\n        return $this->getInnerIterator()->valid();\n    }\n\n    protected function getLoad()\n    {\n        list($load) = sys_getloadavg();\n\n        return $load;\n    }\n\n    protected function sleep($period)\n    {\n        sleep($period);\n    }\n\n    private function throttle($period = 1)\n    {\n        $this->lastThrottle = time();\n\n        if ($this->threshold <= $this->getLoad()) {\n            $this->sleep($period);\n            $this->throttle($period * 2);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/Batch/Strategy/ChunkStrategyTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test\\Batch\\Strategy;\n\nuse Spork\\Batch\\Strategy\\ChunkStrategy;\n\nclass ChunkStrategyTest extends \\PHPUnit_Framework_TestCase\n{\n    /**\n     * @dataProvider provideNumber\n     */\n    public function testChunkArray($number, $expectedCounts)\n    {\n        $strategy = new ChunkStrategy($number);\n        $batches = $strategy->createBatches(range(1, 100));\n\n        $this->assertEquals(count($expectedCounts), count($batches));\n        foreach ($batches as $i => $batch) {\n            $this->assertCount($expectedCounts[$i], $batch);\n        }\n    }\n\n    public function provideNumber()\n    {\n        return array(\n            array(1, array(100)),\n            array(2, array(50, 50)),\n            array(3, array(34, 34, 32)),\n            array(4, array(25, 25, 25, 25)),\n            array(5, array(20, 20, 20, 20, 20)),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/Batch/Strategy/MongoStrategyTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test\\Batch\\Strategy;\n\nuse Spork\\Batch\\Strategy\\MongoStrategy;\nuse Spork\\EventDispatcher\\Events;\nuse Spork\\ProcessManager;\n\nclass MongoStrategyTest extends \\PHPUnit_Framework_TestCase\n{\n    private $mongo;\n    private $manager;\n\n    protected function setUp()\n    {\n        if (!class_exists('MongoClient', false)) {\n            $this->markTestSkipped('Mongo extension is not loaded');\n        }\n\n        try {\n            $this->mongo = new \\MongoClient();\n        } catch (\\MongoConnectionException $e) {\n            $this->markTestSkipped($e->getMessage());\n        }\n\n        $this->manager = new ProcessManager();\n        $this->manager->setDebug(true);\n\n        // close the connection prior to forking\n        $mongo = $this->mongo;\n        $this->manager->addListener(Events::PRE_FORK, function() use($mongo) {\n            $mongo->close();\n        });\n    }\n\n    protected function tearDown()\n    {\n        if ($this->mongo) {\n            $this->mongo->close();\n        }\n\n        unset($this->mongo, $this->manager);\n    }\n\n    public function testBatchJob()\n    {\n        $coll = $this->mongo->spork->widgets;\n\n        $coll->remove();\n        $coll->batchInsert(array(\n            array('name' => 'Widget 1'),\n            array('name' => 'Widget 2'),\n            array('name' => 'Widget 3'),\n        ));\n\n        $this->manager->createBatchJob($coll->find(), new MongoStrategy())\n            ->execute(function($doc) use($coll) {\n                $coll->update(\n                    array('_id' => $doc['_id']),\n                    array('$set' => array('seen' => true))\n                );\n            });\n\n        $this->manager->wait();\n\n        foreach ($coll->find() as $doc) {\n            $this->assertArrayHasKey('seen', $doc);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/Deferred/DeferredAggregateTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test\\Deferred;\n\nuse Spork\\Deferred\\Deferred;\nuse Spork\\Deferred\\DeferredAggregate;\n\nclass DeferredAggregateTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testInvalidChild()\n    {\n        $this->setExpectedException('Spork\\Exception\\UnexpectedTypeException', 'PromiseInterface');\n\n        $defer = new DeferredAggregate(array('asdf'));\n    }\n\n    public function testNoChildren()\n    {\n        $defer = new DeferredAggregate(array());\n\n        $log = array();\n        $defer->done(function() use(& $log) {\n            $log[] = 'done';\n        });\n\n        $this->assertEquals(array('done'), $log);\n    }\n\n    public function testResolvedChildren()\n    {\n        $child = new Deferred();\n        $child->resolve();\n\n        $defer = new DeferredAggregate(array($child));\n\n        $log = array();\n        $defer->done(function() use(& $log) {\n            $log[] = 'done';\n        });\n\n        $this->assertEquals(array('done'), $log);\n    }\n\n    public function testResolution()\n    {\n        $child1 = new Deferred();\n        $child2 = new Deferred();\n\n        $defer = new DeferredAggregate(array($child1, $child2));\n\n        $log = array();\n        $defer->done(function() use(& $log) {\n            $log[] = 'done';\n        });\n\n        $this->assertEquals(array(), $log);\n\n        $child1->resolve();\n        $this->assertEquals(array(), $log);\n\n        $child2->resolve();\n        $this->assertEquals(array('done'), $log);\n    }\n\n    public function testRejection()\n    {\n        $child1 = new Deferred();\n        $child2 = new Deferred();\n        $child3 = new Deferred();\n\n        $defer = new DeferredAggregate(array($child1, $child2, $child3));\n\n        $log = array();\n        $defer->then(function() use(& $log) {\n            $log[] = 'done';\n        }, function() use(& $log) {\n            $log[] = 'fail';\n        });\n\n        $this->assertEquals(array(), $log);\n\n        $child1->resolve();\n        $this->assertEquals(array(), $log);\n\n        $child2->reject();\n        $this->assertEquals(array('fail'), $log);\n\n        $child3->resolve();\n        $this->assertEquals(array('fail'), $log);\n    }\n\n    public function testNested()\n    {\n        $child1a = new Deferred();\n        $child1b = new Deferred();\n        $child1 = new DeferredAggregate(array($child1a, $child1b));\n        $child2 = new Deferred();\n\n        $defer = new DeferredAggregate(array($child1, $child2));\n\n        $child1a->resolve();\n        $child1b->resolve();\n        $child2->resolve();\n\n        $this->assertEquals('resolved', $defer->getState());\n    }\n\n    public function testFail()\n    {\n        $child = new Deferred();\n        $defer = new DeferredAggregate(array($child));\n\n        $log = array();\n        $defer->fail(function() use(& $log) {\n            $log[] = 'fail';\n        });\n\n        $child->reject();\n\n        $this->assertEquals(array('fail'), $log);\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/Deferred/DeferredTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test\\Deferred;\n\nuse Spork\\Deferred\\Deferred;\n\nclass DeferredTest extends \\PHPUnit_Framework_TestCase\n{\n    private $defer;\n\n    protected function setUp()\n    {\n        $this->defer = new Deferred();\n    }\n\n    protected function tearDown()\n    {\n        unset($this->defer);\n    }\n\n    /**\n     * @dataProvider getMethodAndKey\n     */\n    public function testCallbackOrder($method, $expected)\n    {\n        $log = array();\n\n        $this->defer->always(function() use(& $log) {\n            $log[] = 'always';\n            $log[] = func_get_args();\n        })->done(function() use(& $log) {\n            $log[] = 'done';\n            $log[] = func_get_args();\n        })->fail(function() use(& $log) {\n            $log[] = 'fail';\n            $log[] = func_get_args();\n        });\n\n        $this->defer->$method(1, 2, 3);\n\n        $this->assertEquals(array(\n            'always',\n            array(1, 2, 3),\n            $expected,\n            array(1, 2, 3),\n        ), $log);\n    }\n\n    /**\n     * @dataProvider getMethodAndKey\n     */\n    public function testThen($method, $expected)\n    {\n        $log = array();\n\n        $this->defer->then(function() use(& $log) {\n            $log[] = 'done';\n        }, function() use(& $log) {\n            $log[] = 'fail';\n        });\n\n        $this->defer->$method();\n\n        $this->assertEquals(array($expected), $log);\n    }\n\n    /**\n     * @dataProvider getMethod\n     */\n    public function testMultipleResolve($method)\n    {\n        $log = array();\n\n        $this->defer->always(function() use(& $log) {\n            $log[] = 'always';\n        });\n\n        $this->defer->$method();\n        $this->defer->$method();\n\n        $this->assertEquals(array('always'), $log);\n    }\n\n    /**\n     * @dataProvider getMethodAndInvalid\n     */\n    public function testInvalidResolve($method, $invalid)\n    {\n        $this->setExpectedException('LogicException', 'that has already been');\n\n        $this->defer->$method();\n        $this->defer->$invalid();\n    }\n\n    /**\n     * @dataProvider getMethodAndQueue\n     */\n    public function testAlreadyResolved($resolve, $queue, $expect = true)\n    {\n        // resolve the object\n        $this->defer->$resolve();\n\n        $log = array();\n        $this->defer->$queue(function() use(& $log, $queue) {\n            $log[] = $queue;\n        });\n\n        $this->assertEquals($expect ? array($queue) : array(), $log);\n    }\n\n    /**\n     * @dataProvider getMethodAndInvalidCallback\n     */\n    public function testInvalidCallback($method, $invalid)\n    {\n        $this->setExpectedException('Spork\\Exception\\UnexpectedTypeException', 'callable');\n\n        $this->defer->$method($invalid);\n    }\n\n    // providers\n\n    public function getMethodAndKey()\n    {\n        return array(\n            array('resolve', 'done'),\n            array('reject', 'fail'),\n        );\n    }\n\n    public function getMethodAndInvalid()\n    {\n        return array(\n            array('resolve', 'reject'),\n            array('reject', 'resolve'),\n        );\n    }\n\n    public function getMethodAndQueue()\n    {\n        return array(\n            array('resolve', 'always'),\n            array('resolve', 'done'),\n            array('resolve', 'fail', false),\n            array('reject', 'always'),\n            array('reject', 'done', false),\n            array('reject', 'fail'),\n        );\n    }\n\n    public function getMethodAndInvalidCallback()\n    {\n        return array(\n            array('always', 'foo!'),\n            array('always', array('foo!')),\n            array('done', 'foo!'),\n            array('done', array('foo!')),\n            array('fail', 'foo!'),\n            array('fail', array('foo!')),\n        );\n    }\n\n    public function getMethod()\n    {\n        return array(\n            array('resolve'),\n            array('reject'),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/ProcessManagerTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test;\n\nuse Spork\\Fork;\nuse Spork\\ProcessManager;\n\nclass ProcessManagerTest extends \\PHPUnit_Framework_TestCase\n{\n    /**\n     * Process Manager object\n     *\n     * @var ProcessManager\n     */\n    private $manager;\n\n    protected function setUp()\n    {\n        $this->manager = new ProcessManager();\n    }\n\n    protected function tearDown()\n    {\n        unset($this->manager);\n    }\n\n    public function testDoneCallbacks()\n    {\n        $success = null;\n\n        $fork = $this->manager->fork(function() {\n            echo 'output';\n            return 'result';\n        })->done(function() use(& $success) {\n            $success = true;\n        })->fail(function() use(& $success) {\n            $success = false;\n        });\n\n        $this->manager->wait();\n\n        $this->assertTrue($success);\n        $this->assertEquals('output', $fork->getOutput());\n        $this->assertEquals('result', $fork->getResult());\n    }\n\n    public function testFailCallbacks()\n    {\n        $success = null;\n\n        $fork = $this->manager->fork(function() {\n            throw new \\Exception('child error');\n        })->done(function() use(& $success) {\n            $success = true;\n        })->fail(function() use(& $success) {\n            $success = false;\n        });\n\n        $this->manager->wait();\n\n        $this->assertFalse($success);\n        $this->assertNotEmpty($fork->getError());\n    }\n\n    public function testObjectReturn()\n    {\n        $fork = $this->manager->fork(function() {\n            return new Unserializable();\n        });\n\n        $this->manager->wait();\n\n        $this->assertNull($fork->getResult());\n        $this->assertFalse($fork->isSuccessful());\n    }\n\n    public function testBatchProcessing()\n    {\n        $expected = range(100, 109);\n\n        $fork = $this->manager->process($expected, function($item) {\n            return $item;\n        });\n\n        $this->manager->wait();\n\n        $this->assertEquals($expected, $fork->getResult());\n    }\n\n    /**\n     * Test batch processing with return values containing a newline character\n     */\n    public function testBatchProcessingWithNewlineReturnValues()\n    {\n        $range = range(100, 109);\n        $expected = array (\n            0 => \"SomeString\\n100\",\n            1 => \"SomeString\\n101\",\n            2 => \"SomeString\\n102\",\n            3 => \"SomeString\\n103\",\n            4 => \"SomeString\\n104\",\n            5 => \"SomeString\\n105\",\n            6 => \"SomeString\\n106\",\n            7 => \"SomeString\\n107\",\n            8 => \"SomeString\\n108\",\n            9 => \"SomeString\\n109\",\n        );\n\n        $this->manager->setDebug(true);\n        $fork = $this->manager->process($range, function($item) {\n            return \"SomeString\\n$item\";\n        });\n\n        $this->manager->wait();\n\n        $this->assertEquals($expected, $fork->getResult());\n    }\n\n    /**\n     * Data provider for `testLargeBatchProcessing()`\n     *\n     * @return array\n     */\n    public function batchProvider()\n    {\n        return array(\n            array(10),\n            array(1000),\n            array(6941),\n            array(6942),\n            array(6000),\n            array(10000),\n            array(20000),\n        );\n    }\n\n    /**\n     * Test large batch sizes\n     *\n     * @dataProvider batchProvider\n     */\n    public function testLargeBatchProcessing($rangeEnd)\n    {\n        $expected = array_fill(0, $rangeEnd, null);\n\n        /** @var Fork $fork */\n        $fork = $this->manager->process($expected, function($item) {\n            return $item;\n        });\n\n        $this->manager->wait();\n\n        $this->assertEquals($expected, $fork->getResult());\n    }\n}\n\nclass Unserializable\n{\n    public function __sleep()\n    {\n        throw new \\Exception('Hey, don\\'t serialize me!');\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/SignalTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test;\n\nuse Spork\\ProcessManager;\n\nclass SignalTest extends \\PHPUnit_Framework_TestCase\n{\n    private $manager;\n\n    protected function setUp()\n    {\n        $this->manager = new ProcessManager();\n    }\n\n    protected function tearDown()\n    {\n        $this->manager = null;\n    }\n\n    public function testSignalParent()\n    {\n        $signaled = false;\n        $this->manager->addListener(SIGUSR1, function() use(& $signaled) {\n            $signaled = true;\n        });\n\n        $this->manager->fork(function($sharedMem) {\n            $sharedMem->signal(SIGUSR1);\n        });\n\n        $this->manager->wait();\n\n        $this->assertTrue($signaled);\n    }\n}\n"
  },
  {
    "path": "tests/Spork/Test/Util/ThrottleIteratorTest.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Spork\\Test\\Util;\n\nuse Spork\\Util\\ThrottleIterator;\n\nclass ThrottleIteratorTest extends \\PHPUnit_Framework_TestCase\n{\n    private $iterator;\n\n    protected function setUp()\n    {\n        $this->iterator = new ThrottleIteratorStub(array(1, 2, 3, 4, 5), 3);\n        $this->iterator->loads = array(4, 4, 4, 1, 1);\n    }\n\n    protected function tearDown()\n    {\n        unset($this->iterator);\n    }\n\n    public function testIteration()\n    {\n        iterator_to_array($this->iterator);\n        $this->assertEquals(array(1, 2, 4), $this->iterator->sleeps);\n    }\n}\n\nclass ThrottleIteratorStub extends ThrottleIterator\n{\n    public $loads = array();\n    public $sleeps = array();\n\n    protected function getLoad()\n    {\n        return (integer) array_shift($this->loads);\n    }\n\n    protected function sleep($period)\n    {\n        $this->sleeps[] = $period;\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nif (!$loader = @include __DIR__.'/../vendor/autoload.php') {\n    echo <<<EOM\nYou must set up the project dependencies by running the following commands:\n\n    curl -s http://getcomposer.org/installer | php\n    php composer.phar install\n\nEOM;\n\n    exit(1);\n}\n\n$loader->add('Spork\\Test', __DIR__);\n"
  }
]