[
  {
    "path": ".editorconfig",
    "content": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and documentation with \"export-ignore\".\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/.travis.yml        export-ignore\n/phpunit.xml.dist   export-ignore\n/.scrutinizer.yml   export-ignore\n/tests              export-ignore\n/.editorconfig      export-ignore\n"
  },
  {
    "path": ".gitignore",
    "content": "build\ncomposer.lock\ndocs\nvendor\ncoverage"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "filter:\n    excluded_paths: [tests/*]\n\nchecks:\n    php:\n        remove_extra_empty_lines: true\n        remove_php_closing_tag: true\n        remove_trailing_whitespace: true\n        fix_use_statements:\n            remove_unused: true\n            preserve_multiple: false\n            preserve_blanklines: true\n            order_alphabetically: true\n        fix_php_opening_tag: true\n        fix_linefeed: true\n        fix_line_ending: true\n        fix_identation_4spaces: true\n        fix_doc_comments: true\n\n"
  },
  {
    "path": ".styleci.yml",
    "content": "preset: laravel\n\ndisabled:\n  - single_class_element_per_statement\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nphp:\n  - 7.1\n  - 7.2\n  - 7.3\n\nbefore_script:\n  - travis_retry composer self-update\n  - travis_retry composer update --no-interaction\n\nscript:\n  - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover\n\nafter_script:\n  - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to `laravel-tinker-server` will be documented in this file\n\n## 1.0.0 - 201X-XX-XX\n\n- initial release\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contribution guide before creating an issue or pull request.\n\n## Etiquette\n\nThis project is open source, and as such, the maintainers give their free time to build and maintain the source code\nheld within. They make the code freely available in the hope that it will be of use to other developers. It would be\nextremely unfair for them to suffer abuse or anger for their hard work.\n\nPlease be considerate towards maintainers when raising issues or presenting pull requests. Let's show the\nworld that developers are civilized and selfless people.\n\nIt's the duty of the maintainer to ensure that all submissions to the project are of sufficient\nquality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.\n\n## Viability\n\nWhen requesting or submitting new features, first consider whether it might be useful to others. Open\nsource projects are used by many developers, who may have entirely different needs to your own. Think about\nwhether or not your feature is likely to be used by other users of the project.\n\n## Procedure\n\nBefore filing an issue:\n\n- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.\n- Check to make sure your feature suggestion isn't already present within the project.\n- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.\n- Check the pull requests tab to ensure that the feature isn't already in progress.\n\nBefore submitting a pull request:\n\n- Check the codebase to ensure that your feature doesn't already exist.\n- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.\n\n## Requirements\n\nIf the project maintainer has any additional requirements, you will find them listed here.\n\n- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).\n\n- **Add tests!** - Your patch won't be accepted if it doesn't have tests.\n\n- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.\n\n- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.\n\n- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.\n\n- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.\n\n**Happy coding**!\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) Marcel Pociot\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\nfurnished to 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 THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Laravel Tinker Server\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/beyondcode/laravel-tinker-server.svg?style=flat-square)](https://packagist.org/packages/beyondcode/laravel-tinker-server)\n[![Build Status](https://img.shields.io/travis/beyondcode/laravel-tinker-server/master.svg?style=flat-square)](https://travis-ci.org/beyondcode/laravel-tinker-server)\n[![Quality Score](https://img.shields.io/scrutinizer/g/beyondcode/laravel-tinker-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/beyondcode/laravel-tinker-server)\n[![Total Downloads](https://img.shields.io/packagist/dt/beyondcode/laravel-tinker-server.svg?style=flat-square)](https://packagist.org/packages/beyondcode/laravel-tinker-server)\n\nThis package will give you a tinker server, that collects all your `tinker` call outputs **and** allows you to interact with the variables on the fly.\n\n![](https://beyondco.de/github/tinker-server/tinker-server-demo.gif)\n\n## About this package\n\nThis package was built as part of my [PHP Package Development](https://phppackagedevelopment.com) video course. Register for the course to learn how this package was built.\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require beyondcode/laravel-tinker-server\n```\n\nThe package will register itself automatically.\n\nOptionally you can publish the package configuration using:\n\n```bash\nphp artisan vendor:publish --provider=BeyondCode\\\\LaravelTinkerServer\\\\LaravelTinkerServerServiceProvider\n```\n\nThis will publish a file called `laravel-tinker-server.php` in your `config` folder.\n\nIn the config file, you can specify the dump server host that you want to listen on, in case you want to change the default value.\n\n## Usage\n\nStart the tinker server by calling the artisan command:\n\n```bash\nphp artisan tinker-server\n```\n\nAnd then you can put `tinker` calls in your methods to dump variable content as well as instantly making them available in an interactive REPL shell.\n\n```php\n$user = App\\User::find(1);\n\ntinker($user);\n```\n\nIn addition to the `tinker` method, there is also a `td` method, that behaves similar to `dd`. It tinkers the variable and dies the current request.\n\n### Testing\n\n``` bash\ncomposer test\n```\n\n### Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n### Security\n\nIf you discover any security related issues, please email marcel@beyondco.de instead of using the issue tracker.\n\n## Credits\n\n- [Marcel Pociot](https://github.com/mpociot)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n\n## Laravel Package Boilerplate\n\nThis package was generated using the [Laravel Package Boilerplate](https://laravelpackageboilerplate.com).\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"beyondcode/laravel-tinker-server\",\n    \"description\": \"Tinker with your variables while working on your application.\",\n    \"keywords\": [\n        \"beyondcode\",\n        \"laravel-tinker-server\"\n    ],\n    \"homepage\": \"https://github.com/beyondcode/laravel-tinker-server\",\n    \"license\": \"MIT\",\n    \"type\": \"library\",\n    \"authors\": [\n        {\n            \"name\": \"Marcel Pociot\",\n            \"email\": \"marcel@beyondco.de\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^7.1\",\n        \"clue/stdio-react\": \"^2.2\",\n        \"illuminate/support\": \"5.6.*|5.7.*|5.8.*|6.*\",\n        \"laravel/tinker\": \"^1.0\",\n        \"psy/psysh\": \"^0.9.9\",\n        \"react/socket\": \"^1.2\"\n    },\n    \"require-dev\": {\n        \"orchestra/testbench\": \"3.7.*\",\n        \"phpunit/phpunit\": \"^7.0\"\n    },\n    \"autoload\": {\n        \"files\": [\n            \"src/helpers.php\"\n        ],\n        \"psr-4\": {\n            \"BeyondCode\\\\LaravelTinkerServer\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"BeyondCode\\\\LaravelTinkerServer\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"scripts\": {\n        \"test\": \"vendor/bin/phpunit\",\n        \"test-coverage\": \"vendor/bin/phpunit --coverage-html coverage\"\n\n    },\n    \"config\": {\n        \"sort-packages\": true\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"BeyondCode\\\\LaravelTinkerServer\\\\LaravelTinkerServerServiceProvider\"\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": "config/config.php",
    "content": "<?php\n\nreturn [\n    /*\n     * The host to use when listening for debug server connections.\n     */\n    'host' => 'tcp://127.0.0.1:9914',\n];\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         colors=\"true\"\n         verbose=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\">\n    <testsuites>\n        <testsuite name=\"Test Suite\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">src/</directory>\n        </whitelist>\n    </filter>\n    <logging>\n        <log type=\"tap\" target=\"build/report.tap\"/>\n        <log type=\"junit\" target=\"build/report.junit.xml\"/>\n        <log type=\"coverage-html\" target=\"build/coverage\"/>\n        <log type=\"coverage-text\" target=\"build/coverage.txt\"/>\n        <log type=\"coverage-clover\" target=\"build/logs/clover.xml\"/>\n    </logging>\n</phpunit>\n"
  },
  {
    "path": "src/Connection.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer;\n\nclass Connection\n{\n    protected $socket;\n\n    protected $host;\n\n    public function __construct($host)\n    {\n        $this->host = $host;\n    }\n\n    public function write(array $namedParameters): bool\n    {\n        if (! $this->socket = $this->socket ?: $this->createSocket()) {\n            return false;\n        }\n\n        set_error_handler([self::class, 'nullErrorHandler']);\n\n        try {\n            $encodedPayload = base64_encode(serialize($namedParameters)).\"\\n\";\n\n            if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {\n                return true;\n            }\n\n            stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);\n            fclose($this->socket);\n            $this->socket = $this->createSocket();\n\n            if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {\n                return true;\n            }\n        } finally {\n            restore_error_handler();\n        }\n\n        return false;\n    }\n\n    private static function nullErrorHandler($t, $m)\n    {\n        // no-op\n    }\n\n    private function createSocket()\n    {\n        set_error_handler([self::class, 'nullErrorHandler']);\n        try {\n            return stream_socket_client($this->host, $errno, $errstr, 3, STREAM_CLIENT_CONNECT);\n        } finally {\n            restore_error_handler();\n        }\n    }\n}\n"
  },
  {
    "path": "src/Console/TinkerServerCommand.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer\\Console;\n\nuse BeyondCode\\LaravelTinkerServer\\Server;\nuse Illuminate\\Console\\Command;\nuse Laravel\\Tinker\\ClassAliasAutoloader;\nuse Psy\\Configuration;\nuse Psy\\Shell;\nuse Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle;\nuse Symfony\\Component\\Console\\Output\\BufferedOutput;\n\nclass TinkerServerCommand extends Command\n{\n    protected $signature = 'tinker-server';\n\n    public function handle()\n    {\n        $output = $this->createWarningFormatter();\n\n        $server = new Server(config('laravel-tinker-server.host'), $this->createPsyShell(), $output);\n\n        $server->start();\n    }\n\n    protected function createWarningFormatter(): BufferedOutput\n    {\n        $output = new BufferedOutput();\n\n        if (! $output->getFormatter()->hasStyle('warning')) {\n            $style = new OutputFormatterStyle('yellow');\n\n            $output->getFormatter()->setStyle('warning', $style);\n        }\n\n        return $output;\n    }\n\n    protected function createPsyShell()\n    {\n        $config = new Configuration([\n            'updateCheck' => 'never',\n        ]);\n\n        $config->getPresenter()->addCasters(\n            $this->getCasters()\n        );\n\n        $shell = new Shell($config);\n\n        $path = $this->getLaravel()->basePath().DIRECTORY_SEPARATOR.'vendor/composer/autoload_classmap.php';\n\n        ClassAliasAutoloader::register($shell, $path);\n\n        return $shell;\n    }\n\n    protected function getCasters()\n    {\n        $casters = [\n            'Illuminate\\Support\\Collection' => 'Laravel\\Tinker\\TinkerCaster::castCollection',\n        ];\n        if (class_exists('Illuminate\\Database\\Eloquent\\Model')) {\n            $casters['Illuminate\\Database\\Eloquent\\Model'] = 'Laravel\\Tinker\\TinkerCaster::castModel';\n        }\n        if (class_exists('Illuminate\\Foundation\\Application')) {\n            $casters['Illuminate\\Foundation\\Application'] = 'Laravel\\Tinker\\TinkerCaster::castApplication';\n        }\n\n        return $casters;\n    }\n}\n"
  },
  {
    "path": "src/LaravelTinkerServerServiceProvider.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer;\n\nuse BeyondCode\\LaravelTinkerServer\\Console\\TinkerServerCommand;\nuse Illuminate\\Support\\ServiceProvider;\n\nclass LaravelTinkerServerServiceProvider extends ServiceProvider\n{\n    /**\n     * Bootstrap the application services.\n     */\n    public function boot()\n    {\n        if ($this->app->runningInConsole()) {\n            $this->publishes([\n                __DIR__.'/../config/config.php' => config_path('laravel-tinker-server.php'),\n            ], 'config');\n\n            // Registering package commands.\n            $this->commands([\n                TinkerServerCommand::class,\n            ]);\n        }\n    }\n\n    /**\n     * Register the application services.\n     */\n    public function register()\n    {\n        // Automatically apply the package configuration\n        $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'laravel-tinker-server');\n    }\n}\n"
  },
  {
    "path": "src/Server.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer;\n\nuse BeyondCode\\LaravelTinkerServer\\Shell\\ExecutionClosure;\nuse Clue\\React\\Stdio\\Stdio;\nuse Psy\\Shell;\nuse React\\EventLoop\\Factory;\nuse React\\EventLoop\\LoopInterface;\nuse React\\Socket\\ConnectionInterface;\nuse React\\Socket\\Server as SocketServer;\nuse Symfony\\Component\\Console\\Output\\BufferedOutput;\n\nclass Server\n{\n    protected $host;\n\n    /** @var LoopInterface */\n    protected $loop;\n\n    /** @var BufferedOutput */\n    protected $shellOutput;\n\n    /** @var Shell */\n    protected $shell;\n\n    /** @var BufferedOutput */\n    protected $output;\n\n    /** @var Stdio */\n    protected $stdio;\n\n    public function __construct($host, Shell $shell, BufferedOutput $output, LoopInterface $loop = null, Stdio $stdio = null)\n    {\n        $this->host = $host;\n        $this->loop = $loop ?? Factory::create();\n        $this->shell = $shell;\n        $this->output = $output;\n        $this->shellOutput = new BufferedOutput();\n        $this->stdio = $stdio ?? $this->createStdio();\n    }\n\n    public function start()\n    {\n        $this->shell->setOutput($this->shellOutput);\n\n        $this->createSocketServer();\n\n        $this->loop->run();\n    }\n\n    protected function createSocketServer()\n    {\n        $socket = new SocketServer($this->host, $this->loop);\n\n        $socket->on('connection', function (ConnectionInterface $connection) {\n            $connection->on('data', function ($data) use ($connection) {\n                $unserializedData = unserialize(base64_decode($data));\n\n                $this->shell->setScopeVariables(array_merge($unserializedData, $this->shell->getScopeVariables()));\n\n                $this->stdio->write(PHP_EOL);\n\n                collect($unserializedData)->keys()->map(function ($variableName) {\n                    $this->output->writeln('>> $'.$variableName);\n\n                    $this->executeCode('$'.$variableName);\n\n                    $this->output->write($this->shellOutput->fetch());\n\n                    $this->stdio->write($this->output->fetch());\n                });\n            });\n        });\n    }\n\n    protected function createStdio(): Stdio\n    {\n        $stdio = new Stdio($this->loop);\n\n        $stdio->getReadline()->setPrompt('>> ');\n\n        $stdio->on('data', function ($line) use ($stdio) {\n            $line = rtrim($line, \"\\r\\n\");\n\n            $stdio->getReadline()->addHistory($line);\n\n            $this->executeCode($line);\n\n            $this->output->write(PHP_EOL.$this->shellOutput->fetch());\n\n            $this->stdio->write($this->output->fetch());\n        });\n\n        return $stdio;\n    }\n\n    protected function executeCode($code)\n    {\n        (new ExecutionClosure($this->shell, $code))->execute();\n    }\n}\n"
  },
  {
    "path": "src/Shell/ExecutionClosure.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer\\Shell;\n\nuse Psy\\CodeCleaner\\NoReturnValue;\nuse Psy\\Exception\\BreakException;\nuse Psy\\Exception\\ErrorException;\nuse Psy\\Exception\\ThrowUpException;\nuse Psy\\Exception\\TypeErrorException;\nuse Psy\\ExecutionClosure as BaseExecutionClosure;\nuse Psy\\Shell;\n\nclass ExecutionClosure extends BaseExecutionClosure\n{\n    public function __construct(Shell $__psysh__, $__line__)\n    {\n        $this->setClosure($__psysh__, function () use ($__psysh__, $__line__) {\n            try {\n                try {\n                    // Restore execution scope variables\n                    \\extract($__psysh__->getScopeVariables(false), EXTR_SKIP);\n\n                    $_ = new NoReturnValue();\n\n                    $__psysh__->addCode($__line__);\n\n                    // Convert all errors to exceptions\n                    \\set_error_handler([$__psysh__, 'handleError']);\n\n                    // Evaluate the current code buffer\n                    $_ = eval($__psysh__->onExecute($__psysh__->flushCode() ?: self::NOOP_INPUT));\n                } catch (\\Throwable $_e) {\n                    // Clean up on our way out.\n                    \\restore_error_handler();\n\n                    throw $_e;\n                } catch (\\Exception $_e) {\n                    // Clean up on our way out.\n                    \\restore_error_handler();\n\n                    throw $_e;\n                }\n            } catch (BreakException $_e) {\n                $__psysh__->writeException($_e);\n\n                return;\n            } catch (ThrowUpException $_e) {\n                $__psysh__->writeException($_e);\n\n                throw $_e;\n            } catch (\\TypeError $_e) {\n                $__psysh__->writeException(TypeErrorException::fromTypeError($_e));\n            } catch (\\Error $_e) {\n                $__psysh__->writeException(ErrorException::fromError($_e));\n            } catch (\\Exception $_e) {\n                $__psysh__->writeException($_e);\n            }\n\n            \\restore_error_handler();\n\n            $__psysh__->writeReturnValue($_);\n\n            // Save execution scope variables for next time\n            $__psysh__->setScopeVariables(\\get_defined_vars());\n\n            return $_;\n        });\n    }\n}\n"
  },
  {
    "path": "src/helpers.php",
    "content": "<?php\n\nif (! function_exists('tinker')) {\n    function tinker(...$args)\n    {\n        /*\n         * Thank you Caleb\n         * See: https://github.com/calebporzio/awesome-helpers/blob/master/src/helpers/tinker.php\n         */\n        $namedParameters = collect(debug_backtrace())\n            ->where('function', 'tinker')->take(1)\n            ->map(function ($slice) {\n                return array_values($slice);\n            })\n            ->mapSpread(function ($filePath, $lineNumber, $function, $args) {\n                return file($filePath)[$lineNumber - 1];\n                // \"    tinker($post, new User);\"\n            })->map(function ($carry) {\n                return str_before(str_after($carry, 'tinker('), ');');\n                // \"$post, new User\"\n            })->flatMap(function ($carry) {\n                return array_map('trim', explode(',', $carry));\n                // [\"post\", \"new User\"]\n            })->map(function ($carry, $index) {\n                return strpos($carry, '$') === 0\n                    ? str_after($carry, '$')\n                    : 'temp'.$index;\n                // [\"post\", \"temp1\"]\n            })\n            ->combine($args)->all();\n\n        $connection = new \\BeyondCode\\LaravelTinkerServer\\Connection(config('laravel-tinker-server.host'));\n\n        if (! $connection->write($namedParameters)) {\n            dump($args);\n        }\n    }\n}\n\nif (! function_exists('td')) {\n    function td(...$args)\n    {\n        tinker($args);\n\n        die(1);\n    }\n}\n"
  },
  {
    "path": "tests/ConnectionTest.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer\\Tests;\n\nuse BeyondCode\\LaravelTinkerServer\\Connection;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Exception\\ProcessTimedOutException;\nuse Symfony\\Component\\Process\\PhpProcess;\nuse Symfony\\Component\\Process\\Process;\n\nclass ConnectionTest extends TestCase\n{\n    const TINKER_SERVER_HOST = 'tcp://127.0.0.1:9914';\n\n    public function testDump()\n    {\n        $connection = new Connection(self::TINKER_SERVER_HOST);\n\n        $dumped = null;\n\n        $process = $this->getServerProcess();\n\n        try {\n            $process->start(function ($type, $buffer) use ($process, &$dumped, $connection) {\n                if (Process::ERR === $type) {\n                    $process->stop();\n                    $this->fail();\n                } elseif (\"READY\\n\" === $buffer) {\n                    usleep(5000);\n                    $result = $connection->write(['i' => 10]);\n\n                    $this->assertTrue($result);\n                } else {\n                    $dumped .= $buffer;\n                }\n            });\n\n            $process->wait();\n        } catch (ProcessTimedOutException $e) {\n            //\n        }\n\n        $this->assertSame(\"\\r\\e[K\n\\r\\e[K>> \\$i\n=> 10\\n\", $dumped);\n    }\n\n    /** @test */\n    public function it_detects_if_the_tinker_server_is_offline()\n    {\n        $connection = new Connection(self::TINKER_SERVER_HOST);\n\n        $start = microtime(true);\n        $this->assertFalse($connection->write([]));\n        $this->assertLessThan(1, microtime(true) - $start);\n    }\n\n    protected function getServerProcess(): Process\n    {\n        $process = new PhpProcess(file_get_contents(__DIR__.'/fixtures/server.php'), null, [\n            'COMPONENT_ROOT' => __DIR__.'/../',\n            'TINKER_SERVER_HOST' => self::TINKER_SERVER_HOST,\n        ]);\n\n        $process->inheritEnvironmentVariables(true);\n\n        return $process->setTimeout(3);\n    }\n}\n"
  },
  {
    "path": "tests/EchoStream.php",
    "content": "<?php\n\nnamespace BeyondCode\\LaravelTinkerServer\\Tests;\n\nuse React\\Stream\\WritableStreamInterface;\n\nclass EchoStream implements WritableStreamInterface\n{\n    public function on($event, callable $listener)\n    {\n        // TODO: Implement on() method.\n    }\n\n    public function once($event, callable $listener)\n    {\n        // TODO: Implement once() method.\n    }\n\n    public function removeListener($event, callable $listener)\n    {\n        // TODO: Implement removeListener() method.\n    }\n\n    public function removeAllListeners($event = null)\n    {\n        // TODO: Implement removeAllListeners() method.\n    }\n\n    public function listeners($event = null)\n    {\n        // TODO: Implement listeners() method.\n    }\n\n    public function emit($event, array $arguments = [])\n    {\n        // TODO: Implement emit() method.\n    }\n\n    public function isWritable()\n    {\n        return true;\n    }\n\n    public function write($data)\n    {\n        echo $data;\n    }\n\n    public function end($data = null)\n    {\n        // TODO: Implement end() method.\n    }\n\n    public function close()\n    {\n        // TODO: Implement close() method.\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/server.php",
    "content": "<?php\n\nuse BeyondCode\\LaravelTinkerServer\\Server;\nuse BeyondCode\\LaravelTinkerServer\\Tests\\EchoStream;\nuse Psy\\Configuration;\nuse Symfony\\Component\\Console\\Output\\BufferedOutput;\n\n$componentRoot = $_SERVER['COMPONENT_ROOT'] ?? __DIR__.'/../..';\n\n$file = $componentRoot.'/vendor/autoload.php';\n\nrequire $file;\n\n$loop = \\React\\EventLoop\\Factory::create();\n\n$output = new BufferedOutput();\n\n$config = new Configuration([\n    'updateCheck' => 'never',\n]);\n\n$stdio = new \\Clue\\React\\Stdio\\Stdio($loop, null, new EchoStream());\n\n$shell = new \\Psy\\Shell($config);\n\n$server = new Server(getenv('TINKER_SERVER_HOST'), $shell, $output, $loop, $stdio);\n\necho \"READY\\n\";\n\n$server->start();\n"
  }
]