[
  {
    "path": ".gitignore",
    "content": "/vendor\ncomposer.phar\ncomposer.lock\n.DS_Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nphp:\n  - 5.4\n  - 5.5\n  - 5.6\n  - hhvm\n\nbefore_script:\n  - travis_retry composer self-update\n  - travis_retry composer install --prefer-source --no-interaction --dev\n\nscript: phpunit\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Ron Shpasser <shpasser@gmail.com>\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\nall copies 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."
  },
  {
    "path": "README.md",
    "content": "# GaeSupport\n\n[![Join the chat at https://gitter.im/shpasser/GaeSupportL5](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/shpasser/GaeSupportL5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Latest Stable Version](https://poser.pugx.org/shpasser/gae-support-l5/v/stable)](https://packagist.org/packages/shpasser/gae-support-l5)\n[![Total Downloads](https://poser.pugx.org/shpasser/gae-support-l5/downloads)](https://packagist.org/packages/shpasser/gae-support-l5)\n[![Latest Unstable Version](https://poser.pugx.org/shpasser/gae-support-l5/v/unstable)](https://packagist.org/packages/shpasser/gae-support-l5)\n[![License](https://poser.pugx.org/shpasser/gae-support-l5/license)](https://packagist.org/packages/shpasser/gae-support-l5)\n\nGoogle App Engine(GAE) Support package for Laravel 5.1.\n\nCurrently supported features:\n- Generation of general configuration files\n- Mail service provider\n- Queue service provider\n- Database connection\n- Filesystem\n\nFor Lumen see https://github.com/shpasser/GaeSupportLumen.\n\n## Installation\n\nPull in the package via Composer.\n\n```js\n\"require\": {\n    \"shpasser/gae-support-l5\": \"~1.0\"\n}\n```\n\nThen include the service provider within `config/app.php`.\n\n```php\n'providers' => [\n    Shpasser\\GaeSupportL5\\GaeSupportServiceProvider::class\n];\n```\n\n## Usage\n\nGenerate the GAE related files/entries.\n\nCommand template:\n```bash\nphp artisan gae:setup --config --cache-config --bucket=\"your-bucket-id\" --db-socket=\"cloud-sql-instance-socket-connection-string\" --db-name=\"cloud-sql-database-name\" --db-host=\"cloud-sql-instance-ipv4-address\" app-id\n```\n\nArguments and Options:\n<pre>\nphp artisan gae:setup [options] [--] app-id\n\nArguments:\n  app-id                     GAE application ID.\n\nOptions:\n      --config               Generate \"app.yaml\" and \"php.ini\" config files.\n      --cache-config         Generate cached Laravel config file for use on Google App Engine.\n      --bucket=BUCKET        Use the specified GCS-bucket instead of the default one.\n      --db-socket=DB-SOCKET  Cloud SQL socket connection string for production environment.\n      --db-name=DB-NAME      Cloud SQL database name.\n      --db-host=DB-HOST      Cloud SQL database host IPv4 address for local environment.\n</pre>\n\n`--cache-config` option generates cached config file for GAE. This option is essential, because cached config file generated by `php artisan config:cache` is not suitable for use on GAE. As well, cached config file generated for GAE probably will not work in local environment. This option should be used to generate cached config file before application is deployed on GAE.\n\n`--bucket` option defines the GCS-bucket ID to be used by the application for storage. Default GCS bucket is configured unless the option is used.\n\nWhen `--db-name` option is defined at least one of `--db-socket` or `--db-host` should\nbe also defined.\n\n`--db-socket` is set using the following format: `/cloudsql/<app-id>:<cloud-sql-instance-name>`. Where `<cloud-sql-instance-name>` is the Cloud SQL instance name and `<app-id>` is the name of the application it belongs to.\n\n### Mail\n\nThe mail driver configuration can be found in `config/mail.php` and `.env.production`,\nthese configuration files are modified / generated by the artisan command. There is\nno need in any kind of custom configuration. All the outgoing mail messages are sent\nwith sender address of an administrator of the application, i.e. `admin@your-app-id.appspotmail.com`.\nThe `sender`, `to`, `cc`, `bcc`, `replyTo`, `subject`, `body` and `attachment`\nparts of email message are supported.\n\n### Queues\n\nThe modified queue configuration file `config/queue.php` should contain:\n\n```php\nreturn array(\n\n\t...\n\n\t/*\n\t|--------------------------------------------------------------------------\n\t| GAE Queue Connection\n\t|--------------------------------------------------------------------------\n\t|\n\t*/\n\n\t'connections' => array(\n\n\t\t'gae' => array(\n\t\t\t'driver'\t=> 'gae',\n\t\t\t'queue'\t\t=> 'default',\n\t\t\t'url'\t\t=> '/tasks',\n\t\t\t'encrypt'\t=> true,\n\t\t),\n\n\t\t...\n\n\t),\n\n);\n```\n\nThe 'default' queue and encryption are used by default.\nIn order to use the queue your `app/Http/routes.php` file should contain the following route:\n\n```php\nRoute::post('tasks', array('as' => 'tasks',\nfunction()\n{\n\treturn Queue::marshal();\n}));\n```\n\nThis route will be used by the GAE queue to push the jobs. Please notice that the route\nand the GAE Queue Connection 'url' parameter point to the same URL. Since the requests\nsubmitted using the route are issued by GAE itself it cannot be CSRF-protected.\nFor more information on the matter please see http://laravel.com/docs/5.0/queues#push-queues.\n\n### Cache, Session and Log\n\nCache, Session and Log components are supported via the use of specific drivers / handlers:\n\n- Cache     - using the 'memcached' driver,\n- Session   - using the 'memcached' driver,\n- Log       - using 'syslog' handler.\n\nThe configuration options for the mentioned drivers / handlers are generated by the artisan command\nand can be found in `.env.production` configuration file.\n\n### Database\n\nGoogle Cloud SQL is supported via Laravel's MySql driver. The connection configuration is added by\nthe artisan command to `config/database.php` under `cloudsql`. The connection parameters can be\nconfigured using `--db-socket`, `--db-name` and `--db-host` options via the artisan command.\n\nThe database related environment variables are set in `.env.production` and `.env.local` files.\n\nThe `production` environment is configured to use the socket connection while the `local` configured\nto connect via the IPv4 address of the Google Cloud SQL instance. Use Google Developers Console in\norder to obtain the socket connection string and enable the IPv4 address of your database instance.\n\nThe migrations are supported while working in `local` environment only.\n\nTo use either the `production` or the `local` environment rename the appropriate file to `.env`.\n\n### Filesystem\n\nIn order to support Laravel filesystem on GAE the artisan command modifies `config/filesystem.php`\nto include an additional disk:\n\n```php\n'gae' => [\n    'driver' => 'gae',\n    'root'   => storage_path().'/app',\n],\n```\n\nand adds the following line to `.env.production` file:\n\n```php\nFILESYSTEM = gae\n```\n\n### Optimizations\n\nThe optimizations allow the application to reduce the use of GCS, which is the only read-write\nstorage available on GAE platform as of now.\n\nIn order to optimize view compilation the included `cachefs` filesystem can be used to store\ncompiled views using `memcached` service. `cachefs` does not provide the application with a\nreliable storage solution, information stored using `memcached` is managed according to\n`memcached` rules and may be deleted when `memcached` decides to. Since the views can\nbe compiled again without any information loss it is appropriate to store compiled\nviews using `cachefs`.\n\n`cachefs` has the following structure:\n\n<pre>\n/\n+-- bootstrap\n    +-- cache\n+-- framework\n    +-- views\n</pre>\n\n'/framework/views' is used to store the compiled views.\n\nUse the following option to enable the feature in `.env.production` and/or `.env.local` file:\n```php\nCACHE_COMPILED_VIEWS = true\n```\n\n'/bootstrap/cache' is used to store the `services.json`, `config.php` and `routes.php` files,\nin order to control caching of these files use the following options in `.env.production` and/or `.env.local` file:\n```php\nCACHE_SERVICES_FILE = true\nCACHE_CONFIG_FILE = true\nCACHE_ROUTES_FILE = true\n```\n\nIn order to use `config.php` first generate it using the `--cache-config` option of\n`php artisan gae:setup` command. `routes.php` has to be generated using\n`php artisan route:cache` command.\n\nCache related options are:\n- supported on GAE and/or in local environment as long as `memcached` service is present,\n- disabled while executing `php artisan gae:setup` command.\n\nAdditionally the initialization of GSC bucket can be skipped to boost the performance.\nIn order to do so, set the following option in the `app.yaml` file:\n```yaml\nenv_variables:\n        GAE_SKIP_GCS_INIT: true\n```\nthe storage path will be set to `/storage` directory of the GCS bucket and storage\ndirectory structure creation will be skipped.\n\nIf not used the filesystem initialization can be removed to minimize GCS usage. In order to\ndo so, remove the following line from `.env.production` file:\n\n```php\nFILESYSTEM = gae\n```\n\n### Artisan Console for GAE\n\nTo support `artisan` commands while running on GAE the package provides `Artisan Console for GAE`.\nThe console is implemented as a separate service and not enabled by default for security reasons. To use the console securely `/artisan` route has to be protected.\n\n#### Installation\n\nInclude the service provider within `config/app.php`.\n\n```php\n'providers' => [\n    Shpasser\\GaeSupportL5\\GaeArtisanConsoleServiceProvider::class\n];\n```\n\nAdd `/artisan` URL handler to `app.yaml` file.\n\n```yaml\nhandlers:\n\n        - url: /artisan\n          script: public/index.php\n          login: admin\n          secure: always\n\n        - url: /.*\n          script: public/index.php\n\n```\n\n`/artisan` URL handler has to appear before the last one (`url: /.*`), otherwise it will be ignored by GAE.\nThe suggested handler secures the route using GAE URL security options. For more information see https://cloud.google.com/appengine/docs/php/config/appconfig#PHP_app_yaml_Secure_URLs.\n\n#### Usage\n\nEnter URL http://your-app-id.appspot.com/artisan in your browser and use the displayed form to submit `artisan` commands.\nSince GAE's filesystem is read-only the commands will not be able to perform write / update operations on it. For the same reason migrations have to be prepared on local development environment before the deployment takes place. Since the console is not really interactive all the commands are executed in non-interactive mode(by automatic appending of `-n` option).\n\n## Deployment\n\nBackup the existing `.env` file if needed and rename the generated `.env.production` to `.env`\nbefore deploying your app.\n\nDownload and install GAE SDK for PHP and deploy your app.\n\n## Known Issues\n\nAs of now Laravel scheduled commands are not supported while running on GAE.\nIn order to use `Artisan Console for GAE` the application class `app/Console/Kernel`\nhas be edited and any of the commands scheduled using its `schedule()` function should be removed.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"shpasser/gae-support-l5\",\n    \"description\": \"Google App Engine Support for Laravel 5.1 apps.\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Ron Shpasser\",\n            \"email\": \"shpasser@gmail.com\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=5.4.0\",\n        \"illuminate/support\": \"~5.0\",\n        \"league/flysystem\": \"~1.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"~4.4\",\n        \"illuminate/console\": \"~5.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Shpasser\\\\\": \"src/Shpasser\"\n        }\n    },\n    \"minimum-stability\": \"stable\"\n}\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\"\n         syntaxCheck=\"false\"\n>\n    <testsuites>\n        <testsuite name=\"Package Test Suite\">\n            <directory suffix=\".php\">./tests/</directory>\n        </testsuite>\n    </testsuites>\n</phpunit>\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Filesystem/GaeAdapter.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Filesystem;\n\nuse League\\Flysystem\\Adapter\\Local;\nuse League\\Flysystem\\Config;\n\n/**\n * Class GaeAdapter\n *\n * The class overrides the existing methods in order to:\n *\n * - remove exclusive locks(not supported by GAE) while writing files,\n *\n * - 'ensureDirectory()' replace a call to 'reapath()' functions with\n * a call to 'gae_realpath()' function, which is compatible with GCS buckets,\n *\n * - 'writeStream()' replace 'fopen()' mode from 'w+', which is not supported\n * on GCS buckets and replaces it with 'w', as for the specific function\n * both 'w+' and 'w' should work properly.\n *\n * - 'applyPathPrefix()' remove trailing directory separators, which prevent\n * listing of disk root directory on GAE. Originally Flysystem Local adapter\n * ends up with path 'gs://bucket/storage/app//' for disk root, then 'is_dir()'\n * is used to check that it is a folder path. The check fails due to the trailing\n * slash which is not supported by GCS and an empty directory listing is returned.\n * In order to make the check pass the path has to be 'gs://bucket/storage/app/'.\n *\n * @package Shpasser\\GaeSupportL5\\Filesystem\n */\nclass GaeAdapter extends Local\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function __construct($root)\n    {\n        parent::__construct($root, 0, self::DISALLOW_LINKS);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function ensureDirectory($root)\n    {\n        if (is_dir($root) === false) {\n            mkdir($root, 0755, true);\n        }\n\n        return gae_realpath($root);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeStream($path, $resource, Config $config)\n    {\n        $location = $this->applyPathPrefix($path);\n        $this->ensureDirectory(dirname($location));\n\n        if (! $stream = fopen($location, 'w')) {\n            return false;\n        }\n\n        while (! feof($resource)) {\n            fwrite($stream, fread($resource, 1024), 1024);\n        }\n\n        if (! fclose($stream)) {\n            return false;\n        }\n\n        if ($visibility = $config->get('visibility')) {\n            $this->setVisibility($path, $visibility);\n        }\n\n        return compact('path', 'visibility');\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function applyPathPrefix($path)\n    {\n        $prefixedPath = parent::applyPathPrefix($path);\n\n        return rtrim($prefixedPath, DIRECTORY_SEPARATOR);\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Foundation/Application.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Foundation;\n\nuse Illuminate\\Foundation\\Application as IlluminateApplication;\nuse Shpasser\\GaeSupportL5\\Storage\\Optimizer;\nuse Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper;\nuse Symfony\\Component\\VarDumper\\Dumper\\CliDumper;\n\nclass Application extends IlluminateApplication\n{\n    /**\n     * AppIdentityService class instantiation is done using the class\n     * name string so we can first check if the class exists and only then\n     * instantiate it.\n     */\n    const GAE_ID_SERVICE = 'google\\appengine\\api\\app_identity\\AppIdentityService';\n\n    /**\n     * The GAE app ID.\n     *\n     * @var string\n     */\n    protected $appId;\n\n    /**\n     * 'true' if running on GAE.\n     * @var boolean\n     */\n    protected $runningOnGae;\n\n    /**\n     * GAE storage bucket path.\n     * @var string\n     */\n    protected $gaeBucketPath;\n\n\n    /**\n     * GAE storage optimizer\n     */\n    protected $optimizer = null;\n\n    /**\n     * Create a new GAE supported application instance.\n     *\n     * @param string $basePath\n     */\n    public function __construct($basePath = null)\n    {\n        $this->gaeBucketPath = null;\n\n        // Load the 'realpath()' function replacement\n        // for GAE storage buckets.\n        require_once(__DIR__ . '/gae_realpath.php');\n\n        $this->detectGae();\n\n        if ($this->isRunningOnGae()) {\n            $this->replaceDefaultSymfonyLineDumpers();\n        }\n\n        $this->optimizer = new Optimizer($basePath, $this->runningInConsole());\n        $this->optimizer->bootstrap();\n\n        parent::__construct($basePath);\n    }\n\n\n    /**\n     * Get the path to the configuration cache file.\n     *\n     * @return string\n     */\n    public function getCachedConfigPath()\n    {\n        $path = $this->optimizer->getCachedConfigPath();\n\n        return $path ?: parent::getCachedConfigPath();\n    }\n\n\n    /**\n     * Get the path to the routes cache file.\n     *\n     * @return string\n     */\n    public function getCachedRoutesPath()\n    {\n        $path = $this->optimizer->getCachedRoutesPath();\n\n        return $path ?: parent::getCachedRoutesPath();\n    }\n\n    /**\n     * Get the path to the cached services.json file.\n     *\n     * @return string\n     */\n    public function getCachedServicesPath()\n    {\n        $path = $this->optimizer->getCachedServicesPath();\n\n        if ($path) {\n            return $path;\n        }\n\n        if ($this->isRunningOnGae()) {\n            return $this->storagePath().'/framework/services.json';\n        }\n\n        return parent::getCachedServicesPath();\n    }\n\n\n    /**\n     * Detect if the application is running on GAE.\n     */\n    protected function detectGae()\n    {\n        if (! class_exists(self::GAE_ID_SERVICE)) {\n            $this->runningOnGae = false;\n            $this->appId = null;\n\n            return;\n        }\n\n        $AppIdentityService = self::GAE_ID_SERVICE;\n        $this->appId = $AppIdentityService::getApplicationId();\n        $this->runningOnGae = ! preg_match('/dev~/', getenv('APPLICATION_ID'));\n    }\n\n    /**\n     * Replaces the default output stream of Symfony's\n     * CliDumper and HtmlDumper classes in order to\n     * be able to run on Google App Engine.\n     *\n     * 'php://stdout' is used by CliDumper,\n     * 'php://output' is used by HtmlDumper,\n     * both are not supported on GAE.\n     */\n    protected function replaceDefaultSymfonyLineDumpers()\n    {\n        HtmlDumper::$defaultOutput =\n        CliDumper::$defaultOutput =\n            function ($line, $depth, $indentPad) {\n                if (-1 !== $depth) {\n                    echo str_repeat($indentPad, $depth).$line.PHP_EOL;\n                }\n            };\n    }\n\n    /**\n     * Returns 'true' if running on GAE.\n     *\n     * @return bool\n     */\n    public function isRunningOnGae()\n    {\n        return $this->runningOnGae;\n    }\n\n    /**\n     * Returns the GAE app ID.\n     *\n     * @return string\n     */\n    public function getGaeAppId()\n    {\n        return $this->appId;\n    }\n\n    /**\n     * Override the storage path\n     *\n     * @return string Storage path URL\n     */\n    public function storagePath()\n    {\n        if ($this->runningOnGae) {\n            if (! is_null($this->gaeBucketPath)) {\n                return $this->gaeBucketPath;\n            }\n\n            $buckets = ini_get('google_app_engine.allow_include_gs_buckets');\n            // Get the first bucket in the list.\n            $bucket = current(explode(', ', $buckets));\n\n            if ($bucket) {\n                $this->gaeBucketPath = \"gs://{$bucket}/storage\";\n\n                if (env('GAE_SKIP_GCS_INIT')) {\n                    return $this->gaeBucketPath;\n                }\n\n                if (! file_exists($this->gaeBucketPath)) {\n                    mkdir($this->gaeBucketPath);\n                    mkdir($this->gaeBucketPath.'/app');\n                    mkdir($this->gaeBucketPath.'/framework');\n                    mkdir($this->gaeBucketPath.'/framework/views');\n                }\n\n                return $this->gaeBucketPath;\n            }\n        }\n\n        return parent::storagePath();\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Foundation/gae_realpath.php",
    "content": "<?php\n\n/**\n * GAE replacement for the original realpath() function.\n */\nfunction gae_realpath($path)\n{\n    $result = realpath($path);\n    if ($result == false) {\n        if (file_exists($path)) {\n            $result = $path;\n        }\n    }\n\n    return $result;\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/GaeArtisanConsoleServiceProvider.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5;\n\nuse Illuminate\\Support\\ServiceProvider;\n\nclass GaeArtisanConsoleServiceProvider extends ServiceProvider\n{\n    /**\n     * Indicates if loading of the provider is deferred.\n     *\n     * @var bool\n     */\n    protected $defer = false;\n\n    /**\n     * Bootstrap any application services.\n     *\n     * @return void\n     */\n    public function boot()\n    {\n        $this->loadViewsFrom(__DIR__.'/../../views', 'gae-support-l5');\n\n        if (! $this->app->routesAreCached()) {\n            require __DIR__.'/Http/routes.php';\n        }\n    }\n\n    /**\n     * Register the service provider.\n     *\n     * @return void\n     */\n    public function register()\n    {\n        //\n    }\n\n    /**\n     * Get the services provided by the provider.\n     *\n     * @return array\n     */\n    public function provides()\n    {\n        return array('gae-artisan-console');\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/GaeSupportServiceProvider.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Illuminate\\Support\\Facades\\Storage;\nuse League\\Flysystem\\Filesystem as Flysystem;\nuse Shpasser\\GaeSupportL5\\Setup\\SetupCommand;\nuse Shpasser\\GaeSupportL5\\Filesystem\\GaeAdapter as GaeFilesystemAdapter;\n\nclass GaeSupportServiceProvider extends ServiceProvider\n{\n    /**\n     * Indicates if loading of the provider is deferred.\n     *\n     * @var bool\n     */\n    protected $defer = false;\n\n    /**\n     * Bootstrap any application services.\n     *\n     * @return void\n     */\n    public function boot()\n    {\n        Storage::extend('gae', function ($app, $config) {\n            return new Flysystem(new GaeFilesystemAdapter($config['root']));\n        });\n    }\n\n    /**\n     * Register the service provider.\n     *\n     * @return void\n     */\n    public function register()\n    {\n        $this->app->singleton('gae.setup', function ($app) {\n            return new SetupCommand;\n        });\n\n        $this->commands('gae.setup');\n    }\n\n    /**\n     * Get the services provided by the provider.\n     *\n     * @return array\n     */\n    public function provides()\n    {\n        return array('gae-support');\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Http/Controllers/ArtisanConsoleController.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Http\\Controllers;\n\nuse Illuminate\\Routing\\Controller;\nuse Symfony\\Component\\Console\\Input\\StringInput;\nuse Symfony\\Component\\Console\\Output\\BufferedOutput;\nuse Illuminate\\Http\\Request;\nuse Artisan;\n\n/**\n * Artisan Console Controller for GAE.\n */\nclass ArtisanConsoleController extends Controller\n{\n    const NON_INTERACTIVE = ' -n';\n\n    /**\n     * Shows the Artisan Console page.\n     * @return mixed view containing the Artisan Console.\n     */\n    public function show()\n    {\n        $command = '';\n        $results = '';\n        return view('gae-support-l5::artisan', compact(['command', 'results']));\n    }\n\n    /**\n     * Executes a command submitted from Artisan Console page.\n     *\n     * @param  Request $request Laravel HTTP request object.\n     * @return mixed view containing the Artisan Console.\n     */\n    public function execute(Request $request)\n    {\n        $command = $request->input('command');\n\n        if ($command === '') {\n            $command = 'list';\n        }\n\n        $output= new BufferedOutput;\n\n        Artisan::handle(new StringInput($command.self::NON_INTERACTIVE), $output);\n        $results = $output->fetch();\n\n        return view('gae-support-l5::artisan', compact(['command', 'results']));\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Http/routes.php",
    "content": "<?php\n\nuse \\Shpasser\\GaeSupportL5\\Http\\Controllers\\ArtisanConsoleController;\n\n/**\n * Maintenance routes.\n */\nRoute::get('artisan',  array('as' => 'artisan',\n    'uses' => ArtisanConsoleController::class.'@show'));\n\nRoute::post('artisan', array('as' => 'artisan',\n    'uses' => ArtisanConsoleController::class.'@execute'));\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Mail/GaeTransportManager.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Mail;\n\nuse Illuminate\\Mail\\TransportManager;\nuse Shpasser\\GaeSupportL5\\Mail\\Transport\\GaeTransport;\n\nclass GaeTransportManager extends TransportManager\n{\n    protected function createGaeDriver()\n    {\n        return new GaeTransport($this->app);\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Mail/MailServiceProvider.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Mail;\n\nuse Shpasser\\GaeSupportL5\\Mail\\Transport\\GaeTransport;\nuse Illuminate\\Mail\\MailServiceProvider as IlluminateMailServiceProvider;\n\nclass MailServiceProvider extends IlluminateMailServiceProvider\n{\n    /**\n     * Register the Swift Transport instance.\n     *\n     * @return void\n     */\n    protected function registerSwiftTransport()\n    {\n        if ($this->app->isRunningOnGae()) {\n            $this->app['swift.transport'] = $this->app->share(function ($app) {\n                return new GaeTransportManager($app);\n            });\n        } else {\n            parent::registerSwiftTransport();\n        }\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Mail/Transport/GaeTransport.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Mail\\Transport;\n\nuse Swift_Transport;\nuse Swift_Mime_Message;\nuse Swift_Events_EventListener;\nuse Swift_Attachment;\nuse Illuminate\\Support\\Facades\\Log;\nuse Shpasser\\GaeSupportL5\\Foundation\\Application;\n\nrequire_once 'google/appengine/api/mail/Message.php';\nuse google\\appengine\\api\\mail\\Message as GAEMessage;\n\nclass GaeTransport implements Swift_Transport\n{\n    /**\n     *  Application instance.\n     *\n     * @var \\Shpasser\\GaeSupportL5\\Foundation\\Application\n     */\n    protected $app;\n\n    public function __construct(Application $app)\n    {\n        $this->app = $app;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function isStarted()\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function start()\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function stop()\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function send(Swift_Mime_Message $message, &$failedRecipients = null)\n    {\n        try {\n            $to  = implode(', ', array_keys((array) $message->getTo()));\n            $cc  = implode(', ', array_keys((array) $message->getCc()));\n            $bcc = implode(', ', array_keys((array) $message->getBcc()));\n            $replyto = '';\n\n            foreach ((array) $message->getReplyTo() as $address => $name) {\n                $replyto = $address;\n                break;\n            }\n\n            $mail_options = [\n                \"sender\"    => \"admin@{$this->app->getGaeAppId()}.appspotmail.com\",\n                \"to\"        => $to,\n                \"subject\"   => $message->getSubject(),\n                \"htmlBody\"  => $message->getBody()\n            ];\n\n            if ($cc !== '') {\n                $mail_options['cc'] = $cc;\n            }\n\n            if ($bcc !== '') {\n                $mail_options['bcc'] = $bcc;\n            }\n\n            if ($replyto !== '') {\n                $mail_options['replyto'] = $replyto;\n            }\n\n            $attachments = $this->getAttachmentsArray($message);\n            if (count($attachments) > 0) {\n                $mail_options['attachments'] = $attachments;\n            }\n\n            $gae_message = new GAEMessage($mail_options);\n            $gae_message->send();\n        } catch (InvalidArgumentException $ex) {\n            Log::warning(\"Exception sending mail: \".$ex);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function registerPlugin(Swift_Events_EventListener $plugin)\n    {\n        //\n    }\n\n    /**\n     * Returns an array of attachments extracted from\n     * a given mail message.\n     *\n     * @param $message\n     *\n     * @return array\n     */\n    protected function getAttachmentsArray($message)\n    {\n        $attachments = array();\n\n        foreach ($message->getChildren() as $entity) {\n            if ($entity instanceof Swift_Attachment) {\n                $attachments[] = array(\n                    'name' => $entity->getFilename(),\n                    'data' => $entity->getBody(),\n                    'content_id' => \"<{$entity->getContentType()}>\"\n                );\n            }\n        }\n\n        return $attachments;\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Queue/GaeConnector.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Queue\\Connectors\\ConnectorInterface;\nuse Illuminate\\Contracts\\Encryption\\Encrypter as EncrypterContract;\n\nclass GaeConnector implements ConnectorInterface\n{\n    /**\n     * The encrypter instance.\n     *\n     * @var \\Illuminate\\Encryption\\Encrypter\n     */\n    protected $crypt;\n\n    /**\n     * The current request instance.\n     *\n     * @var \\Illuminate\\Http\\Request;\n     */\n    protected $request;\n\n\n    /**\n     * Create a new GAE connector instance.\n     *\n     * @param \\Illuminate\\Contracts\\Encryption\\Encrypter $crypt\n     * @param \\Illuminate\\Http\\Request $request\n     */\n    public function __construct(EncrypterContract $crypt, Request $request)\n    {\n        $this->crypt = $crypt;\n        $this->request = $request;\n    }\n\n    /**\n     * Establish a queue connection.\n     *\n     * @param  array  $config\n     * @return \\Illuminate\\Queue\\QueueInterface\n     */\n    public function connect(array $config)\n    {\n        return new GaeQueue($this->request, $config['queue'], $config['url'], $config['encrypt']);\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Queue/GaeJob.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Queue\\Jobs\\Job;\nuse Illuminate\\Contracts\\Queue\\Job as JobContract;\n\nclass GaeJob extends Job  implements JobContract\n{\n    /**\n     * The Gae queue instance.\n     *\n     * @var \\Shpasser\\GaeSupportL5\\Queue\\GaeQueue\n     */\n    protected $gaeQueue;\n\n    /**\n     * The Gae message instance.\n     *\n     * @var array\n     */\n    protected $job;\n\n    /**\n     * Indicates if the message was a push message.\n     *\n     * @var bool\n     */\n    protected $pushed = false;\n\n    /**\n     * Create a new job instance.\n     *\n     * @param  \\Illuminate\\Container\\Container  $container\n     * @param  \\Shpasser\\GaeSupportL5\\Queue\\GaeQueue  $gaeQueue\n     * @param  object  $job\n     * @param  bool    $pushed\n     */\n    public function __construct(Container $container,\n                                GaeQueue $gaeQueue,\n                                $job,\n                                $pushed = false)\n    {\n        $this->job = $job;\n        $this->gaeQueue = $gaeQueue;\n        $this->pushed = $pushed;\n        $this->container = $container;\n    }\n\n    /**\n     * Fire the job.\n     *\n     * @return void\n     */\n    public function fire()\n    {\n        $this->resolveAndFire(json_decode($this->getRawBody(), true));\n    }\n\n    /**\n     * Get the raw body string for the job.\n     *\n     * @return string\n     */\n    public function getRawBody()\n    {\n        return $this->job->body;\n    }\n\n    /**\n     * Delete the job from the queue.\n     *\n     * @return void\n     */\n    public function delete()\n    {\n        parent::delete();\n\n        if (isset($this->job->pushed)) {\n            return;\n        }\n    }\n\n    /**\n     * Release the job back into the queue.\n     *\n     * @param  int   $delay\n     * @return void\n     */\n    public function release($delay = 0)\n    {\n        if (! $this->pushed) {\n            $this->delete();\n        }\n\n        $this->recreateJob($delay);\n    }\n\n    /**\n     * Release a pushed job back onto the queue.\n     *\n     * @param  int  $delay\n     * @return void\n     */\n    protected function recreateJob($delay)\n    {\n        $payload = json_decode($this->job->body, true);\n\n        array_set($payload, 'attempts', array_get($payload, 'attempts', 1) + 1);\n\n        $this->gaeQueue->recreate(json_encode($payload), $this->getQueue(), $delay);\n    }\n\n    /**\n     * Get the number of times the job has been attempted.\n     *\n     * @return int\n     */\n    public function attempts()\n    {\n        return array_get(json_decode($this->job->body, true), 'attempts', 1);\n    }\n\n    /**\n     * Get the job identifier.\n     *\n     * @return string\n     */\n    public function getJobId()\n    {\n        return $this->job->id;\n    }\n\n    /**\n     * Get the IoC container instance.\n     *\n     * @return \\Illuminate\\Container\\Container\n     */\n    public function getContainer()\n    {\n        return $this->container;\n    }\n\n    /**\n     * Get the underlying Gae queue instance.\n     *\n     * @return \\Shpasser\\GaeSupportL5\\Queue\\GaeQueue\n     */\n    public function getGaeQueue()\n    {\n        return $this->gaeQueue;\n    }\n\n    /**\n     * Get the underlying Gae job.\n     *\n     * @return array\n     */\n    public function getGaeJob()\n    {\n        return $this->job;\n    }\n\n    /**\n     * Get the name of the queue the job belongs to.\n     *\n     * @return string\n     */\n    public function getQueue()\n    {\n        return array_get(json_decode($this->job->body, true), 'queue');\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Queue/GaeQueue.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Queue\\Queue;\nuse Illuminate\\Contracts\\Queue\\Queue as QueueContract;\nuse google\\appengine\\api\\taskqueue\\PushTask;\nuse Log;\n\nclass GaeQueue extends Queue implements QueueContract\n{\n    const PAYLOAD_REQ_PARAM_NAME = 'data';\n\n    /**\n     * The current request instance.\n     *\n     * @var \\Illuminate\\Http\\Request\n     */\n    protected $request;\n\n    /**\n     * The name of the default tube.\n     *\n     * @var string\n     */\n    protected $default;\n\n    /**\n     * URL for push.\n     * @var string\n     */\n    protected $url;\n\n    /**\n     * Indicates if the messages should be encrypted.\n     *\n     * @var bool\n     */\n    protected $shouldEncrypt;\n\n    /**\n     * Create a new Gae queue instance.\n     *\n     * @param  \\Illuminate\\Http\\Request  $request\n     * @param  string  $default\n     * @param  bool  $shouldEncrypt\n     */\n    public function __construct(Request $request, $default, $url, $shouldEncrypt = false)\n    {\n        $this->request = $request;\n        $this->url = $url;\n        $this->default = $default;\n        $this->shouldEncrypt = $shouldEncrypt;\n    }\n\n    /**\n     * Push a new job onto the queue.\n     *\n     * @param  string  $job\n     * @param  mixed   $data\n     * @param  string  $queue\n     * @return mixed\n     */\n    public function push($job, $data = '', $queue = null)\n    {\n        return $this->pushRaw($this->createPayload($job, $data, $queue), $queue);\n    }\n\n    /**\n     * Push a raw payload onto the queue.\n     *\n     * @param  string  $payload\n     * @param  string  $queue\n     * @param  array   $options\n     * @return mixed\n     */\n    public function pushRaw($payload, $queue = null, array $options = array())\n    {\n        if ($this->shouldEncrypt) {\n            $payload = $this->encrypter->encrypt($payload);\n        }\n\n        $task = new PushTask($this->url,\n                             array(self::PAYLOAD_REQ_PARAM_NAME => $payload),\n                             $options);\n        return $task->add($this->getQueue($queue));\n    }\n\n    /**\n     * Push a raw payload onto the queue after encrypting the payload.\n     *\n     * @param  string  $payload\n     * @param  string  $queue\n     * @param  int     $delay\n     * @return mixed\n     */\n    public function recreate($payload, $queue = null, $delay)\n    {\n        $options = array('delay_seconds' => $this->getSeconds($delay));\n\n        return $this->pushRaw($payload, $queue, $options);\n    }\n\n    /**\n     * Push a new job onto the queue after a delay.\n     *\n     * @param  \\DateTime|int  $delay\n     * @param  string  $job\n     * @param  mixed   $data\n     * @param  string  $queue\n     * @return mixed\n     */\n    public function later($delay, $job, $data = '', $queue = null)\n    {\n        $delay_seconds = $this->getSeconds($delay);\n\n        $payload = $this->createPayload($job, $data, $queue);\n\n        return $this->pushRaw($payload, $queue, compact('delay_seconds'));\n    }\n\n    /**\n     * Pop the next job off of the queue.\n     *\n     * @param  string  $queue\n     * @return \\Illuminate\\Queue\\Jobs\\Job|null\n     */\n    public function pop($queue = null)\n    {\n        throw new \\RuntimeException(\"Pop is not supported by GaeQueue.\");\n    }\n\n    /**\n     * Delete a message from the Gae queue.\n     *\n     * @param  string  $queue\n     * @param  string  $id\n     * @return void\n     */\n    public function deleteMessage($queue, $id)\n    {\n        throw new \\RuntimeException(\"Delete is not supported by GaeQueue.\");\n    }\n\n    /**\n     * Marshal a push queue request and fire the job.\n     *\n     * @return \\Illuminate\\Http\\Response\n     */\n    public function marshal()\n    {\n        try {\n            $job = $this->marshalPushedJob();\n        } catch (\\Exception $e) {\n            // Ignore for security reasons!\n            // So if we are being hacked\n            // the hacker would think it went OK.\n            Log::warning('Marshalling Queue Request: Invalid job.');\n            return new Response('OK');\n        }\n\n        if (isset($job->id)) {\n            $this->createPushedGaeJob($job)->fire();\n        } else {\n            Log::warning('Marshalling Queue Request: No GAE header supplied.');\n        }\n\n        return new Response('OK');\n    }\n\n    /**\n     * Marshal out the pushed job and payload.\n     *\n     * @return object\n     */\n    protected function marshalPushedJob()\n    {\n        $r = $this->request;\n\n        $body = $this->parseJobBody($r->input(self::PAYLOAD_REQ_PARAM_NAME));\n\n        return (object) array(\n            'id' => $r->header('X-AppEngine-TaskName'), 'body' => $body, 'pushed' => true,\n        );\n    }\n\n    /**\n     * Create a new GaeJob for a pushed job.\n     *\n     * @param  object  $job\n     * @return \\Shpasser\\GaeSupportL5\\Queue\\GaeJob\n     */\n    protected function createPushedGaeJob($job)\n    {\n        return new GaeJob($this->container, $this, $job, true);\n    }\n\n    /**\n     * Create a payload string from the given job and data.\n     *\n     * @param  string  $job\n     * @param  mixed   $data\n     * @param  string  $queue\n     * @return string\n     */\n    protected function createPayload($job, $data = '', $queue = null)\n    {\n        $payload = $this->setMeta(parent::createPayload($job, $data), 'attempts', 1);\n\n        return $this->setMeta($payload, 'queue', $this->getQueue($queue));\n    }\n\n    /**\n     * Parse the job body for firing.\n     *\n     * @param  string  $body\n     * @return string\n     */\n    protected function parseJobBody($body)\n    {\n        return $this->shouldEncrypt ? $this->encrypter->decrypt($body) : $body;\n    }\n\n    /**\n     * Get the queue or return the default.\n     *\n     * @param  string|null  $queue\n     * @return string\n     */\n    public function getQueue($queue)\n    {\n        return $queue ?: $this->default;\n    }\n\n    /**\n     * Get the request instance.\n     *\n     * @return \\Illuminate\\Http\\Request\n     */\n    public function getRequest()\n    {\n        return $this->request;\n    }\n\n    /**\n     * Set the request instance.\n     *\n     * @param \\Illuminate\\Http\\Request $request\n     */\n    public function setRequest(Request $request)\n    {\n        $this->request = $request;\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Queue/Listener.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Queue\\Listener as IlluminateQueueListener;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\n\n/**\n * Queue Listener class overriding the original one in order to\n * prevent execution of PHP function not supported by GAE, and\n * in particular 'escapeshellarg()'.\n */\nclass Listener extends IlluminateQueueListener\n{\n    /**\n     * Build the environment specific worker command.\n     *\n     * @return string\n     */\n    protected function buildWorkerCommand()\n    {\n        if (! app()->isRunningOnGae()) {\n            return parent::buildWorkerCommand();\n        }\n\n        $binary = (new PhpExecutableFinder)->find(false);\n\n        if (defined('HHVM_VERSION')) {\n            $binary .= ' --php';\n        }\n\n        if (defined('ARTISAN_BINARY')) {\n            $artisan = ARTISAN_BINARY;\n        } else {\n            $artisan = 'artisan';\n        }\n\n        $command = 'queue:work %s --queue=%s --delay=%s --memory=%s --sleep=%s --tries=%s';\n\n        return \"{$binary} {$artisan} {$command}\";\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Queue/QueueServiceProvider.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Queue\\QueueServiceProvider as LaravelQueueServiceProvider;\n\nclass QueueServiceProvider extends LaravelQueueServiceProvider\n{\n    /**\n     * Register the connectors on the queue manager.\n     *\n     * @param  \\Illuminate\\Queue\\QueueManager  $manager\n     * @return void\n     */\n    public function registerConnectors($manager)\n    {\n        parent::registerConnectors($manager);\n        $this->registerGaeConnector($manager);\n    }\n\n    /**\n     * Register the GAE queue connector.\n     *\n     * @param  \\Illuminate\\Queue\\QueueManager  $manager\n     * @return void\n     */\n    protected function registerGaeConnector($manager)\n    {\n        $app = $this->app;\n\n        $manager->addConnector('gae', function () use ($app) {\n            return new GaeConnector($app['encrypter'], $app['request']);\n        });\n    }\n\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Setup/Configurator.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse Illuminate\\Console\\Command;\nuse Artisan;\nuse Dotenv\\Dotenv;\n\n/**\n * Class Configurator\n *\n * @package Shpasser\\GaeSupportL5\\Setup\n */\n\nclass Configurator\n{\n    protected $myCommand;\n\n    /**\n     * Constructs a new instance of Configurator class.\n     *\n     * @param Command $myCommand console\n     * command to be used for console output.\n     */\n    public function __construct(Command $myCommand)\n    {\n        $this->myCommand = $myCommand;\n    }\n\n    /**\n     * Configures a Laravel app to be deployed on GAE.\n     *\n     * @param string $appId the GAE application ID.\n     * @param bool $generateConfig if 'true' => generate GAE config files(app.yaml and php.ini).\n     * @param bool $cacheConfig if 'true' => generate cached config file(config.php).\n     * @param string $bucketId the custom GCS bucket ID, if 'null' the default bucket is used.\n     * @param string $dbSocket Cloud SQL socket connection string.\n     * @param string $dbName Cloud SQL database name.\n     * @param string $dbHost Cloud SQL host IPv4 address.\n     */\n    public function configure($appId, $generateConfig, $cacheConfig, $bucketId,\n                              $dbSocket, $dbName, $dbHost)\n    {\n        $env_file               = app_path().'/../.env';\n        $env_production_file    = app_path().'/../.env.production';\n        $env_local_file         = app_path().'/../.env.local';\n        $bootstrap_app_php      = app_path().'/../bootstrap/app.php';\n        $config_app_php         = app_path().'/../config/app.php';\n        $config_view_php        = app_path().'/../config/view.php';\n        $config_queue_php       = app_path().'/../config/queue.php';\n        $config_database_php    = app_path().'/../config/database.php';\n        $config_filesystems_php = app_path().'/../config/filesystems.php';\n        $cached_config_php      = base_path().'/bootstrap/cache/config.php';\n\n        $this->createEnvProductionFile($env_file, $env_production_file, $dbSocket, $dbName);\n        $this->createEnvLocalFile($env_file, $env_local_file, $dbHost, $dbName);\n        $this->processFile($bootstrap_app_php, ['replaceAppClass']);\n        $this->processFile($config_app_php, [\n           'replaceLaravelServiceProviders',\n           'setLogHandler'\n        ]);\n        $this->processFile($config_view_php, ['replaceCompiledPath']);\n        $this->processFile($config_queue_php, ['addQueueConfig']);\n        $this->processFile($config_database_php, ['addCloudSqlConfig']);\n        $this->processFile($config_filesystems_php, ['addGaeDisk']);\n\n        if ($cacheConfig) {\n            $env = new Dotenv(dirname($env_production_file),\n                              basename($env_production_file));\n            $env->overload();\n\n            $result = Artisan::call('config:cache', array());\n            if ($result === 0) {\n                $this->processFile($cached_config_php, ['fixCachedConfig']);\n            }\n        }\n\n        if ($generateConfig) {\n            $app_yaml   = app_path().'/../app.yaml';\n            $publicPath = app_path().'/../public';\n            $php_ini    = app_path().'/../php.ini';\n\n            $this->generateAppYaml($appId, $app_yaml, $publicPath);\n            $this->generatePhpIni($appId, $bucketId, $php_ini);\n        }\n    }\n\n    /**\n     * Creates a '.env.production' file based on the existing '.env' file.\n     *\n     * @param string $env_file The '.env' file path.\n     * @param string $env_production_file The '.env.production' file path.\n     * @param string $dbSocket Cloud SQL socket connection string.\n     * @param string $dbName Cloud SQL database name.\n     */\n    protected function createEnvProductionFile($env_file, $env_production_file, $dbSocket, $dbName)\n    {\n        if (!file_exists($env_file)) {\n            $this->myCommand->error('Cannot find \".env\" file to import the existing options.');\n            return;\n        }\n\n        if (file_exists($env_production_file)) {\n            $overwrite = $this->myCommand->confirm(\n                'Overwrite the existing \".env.production\" file?', false\n            );\n\n            if (! $overwrite) {\n                return;\n            }\n        }\n\n        $env = new EnvHelper;\n        $env->read($env_file);\n\n        $env['APP_ENV']   = 'production';\n        $env['APP_DEBUG'] = 'false';\n        $env['APP_LOG']   = 'syslog';\n\n        $env['CACHE_DRIVER']   = 'memcached';\n        $env['SESSION_DRIVER'] = 'memcached';\n\n        $env['MAIL_DRIVER']  = 'gae';\n        $env['QUEUE_DRIVER'] = 'gae';\n        $env['FILESYSTEM']   = 'gae';\n\n        if ((! is_null($dbSocket)) && (! is_null($dbName))) {\n            $env['DB_CONNECTION'] = 'cloudsql';\n            $env['DB_SOCKET']     = $dbSocket;\n            $env['DB_HOST']       = '';\n            $env['DB_DATABASE']   = $dbName;\n            $env['DB_USERNAME']   = 'root';\n            $env['DB_PASSWORD']   = '';\n        }\n\n        $this->addOptimizerOptions($env);\n\n        $env->write($env_production_file);\n\n        $this->myCommand->info('Created the \".env.production\" file.');\n    }\n\n    /**\n     * Creates a '.env.local' file based on the existing '.env' file.\n     *\n     * @param string $env_file The '.env' file path.\n     * @param string $env_local_file The '.env.local' file path.\n     * @param string $dbHost Cloud SQL host IPv4 address.\n     * @param string $dbName Cloud SQL database name.\n     */\n    protected function createEnvLocalFile($env_file, $env_local_file, $dbHost, $dbName)\n    {\n        if (!file_exists($env_file)) {\n            $this->myCommand->error('Cannot find \".env\" file to import the existing options.');\n            return;\n        }\n\n        if (file_exists($env_local_file)) {\n            $overwrite = $this->myCommand->confirm(\n                'Overwrite the existing \".env.local\" file?', false\n            );\n\n            if (! $overwrite) {\n                return;\n            }\n        }\n\n        $env = new EnvHelper;\n        $env->read($env_file);\n\n        $env['APP_ENV']   = 'local';\n        $env['APP_DEBUG'] = 'true';\n\n        $env['CACHE_DRIVER']   = 'file';\n        $env['SESSION_DRIVER'] = 'file';\n\n        if ((! is_null($dbHost)) && (! is_null($dbName))) {\n            $env['DB_CONNECTION'] = 'cloudsql';\n            $env['DB_SOCKET']     = '';\n            $env['DB_HOST']       = $dbHost;\n            $env['DB_DATABASE']   = $dbName;\n            $env['DB_USERNAME']   = 'root';\n            $env['DB_PASSWORD']   = 'password';\n        }\n\n        $this->addOptimizerOptions($env);\n\n        $env->write($env_local_file);\n\n        $this->myCommand->info('Created the \".env.local\" file.');\n    }\n\n    /**\n     * Adds 'Optimizer' options to an environment object.\n     *\n     * @param \\Shpasser\\GaeSupportL5\\Setup\\EnvHelper $env\n     * the environment object to modify.\n     */\n    protected function addOptimizerOptions(EnvHelper $env)\n    {\n        $env['CACHE_SERVICES_FILE']  = 'false';\n        $env['CACHE_CONFIG_FILE']    = 'false';\n        $env['CACHE_ROUTES_FILE']    = 'false';\n        $env['CACHE_COMPILED_VIEWS'] = 'false';\n    }\n\n    /**\n     * Processes a given file with given processors.\n     *\n     * @param  string $filePath   the path of the file to be processed.\n     * @param  array  $processors array of processor function names to\n     * be called during the file processing. Every such function shall\n     * receive the file contents string as a parameter and return the\n     * modified file contents.\n     *\n     * <code>\n     * protected function processorFunc($contents)\n     * {\n     *     ...\n     *     return $modified;\n     * }\n     * </code>\n     */\n    protected function processFile($filePath, $processors)\n    {\n        $contents = file_get_contents($filePath);\n\n        $processed = $contents;\n\n        foreach ($processors as $processor) {\n            $processed = $this->$processor($processed);\n        }\n\n        if ($processed === $contents) {\n            return;\n        }\n\n        $this->backupFile($filePath);\n\n        file_put_contents($filePath, $processed);\n    }\n\n    /**\n     * Processor function. Replaces the Laravel\n     * application class with the one compatible with GAE.\n     *\n     * @param string $contents the 'bootstrap/app.php' file contents.\n     *\n     * @return string the modified file contents.\n     */\n    protected function replaceAppClass($contents)\n    {\n        $modified = str_replace(\n            'Illuminate\\Foundation\\Application',\n            'Shpasser\\GaeSupportL5\\Foundation\\Application',\n            $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Replaced the application class in \"bootstrap/app.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Processor function. Replaces the Laravel\n     * service providers with GAE compatible ones.\n     *\n     * @param string $contents the 'config/app.php' file contents.\n     *\n     * @return string the modified file contents.\n     */\n    protected function replaceLaravelServiceProviders($contents)\n    {\n        $strings = [\n            'Illuminate\\Mail\\MailServiceProvider',\n            'Illuminate\\Queue\\QueueServiceProvider'\n        ];\n\n        // Replacement to:\n        //  - additionally support Google App Engine Queues,\n        //  - additionally support Google App Engine Mail.\n        $replacements = [\n            'Shpasser\\GaeSupportL5\\Mail\\MailServiceProvider',\n            'Shpasser\\GaeSupportL5\\Queue\\QueueServiceProvider'\n        ];\n\n        $modified = str_replace($strings, $replacements, $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Replaced the service providers in \"config/app.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Processor function. Sets the syslog log handler\n     * for a Laravel GAE app.\n     *\n     * @param string $contents the 'config/app.php' file contents.\n     *\n     * @return string the modified file contents.\n     */\n    protected function setLogHandler($contents)\n    {\n        $expression = \"/'log'.*=>((?!env\\('APP_LOG').)*'\\b.+?\\b'\\)?/\";\n        $replacement = \"'log' => env('APP_LOG', 'single')\";\n\n        $modified = preg_replace($expression, $replacement, $contents);\n\n        if ($contents !== $modified)\n        {\n            $this->myCommand->info('Set the log handler in \"config/app.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Processor function. Replaces 'compiled' path with GAE\n     * compatible one when running on GAE.\n     *\n     * @param string $contents the 'config/view.php' file contents.\n     * @return string the modified file contents.\n     */\n    protected function replaceCompiledPath($contents)\n    {\n        $expression = \"/'compiled'\\s*=>\\s*(.+(,|(?=\\s+\\]))|[^,]+(,|(?=\\s+\\])))/\";\n        $replacement =\n<<<EOT\n'compiled' => env('CACHE_COMPILED_VIEWS') ?\n                  Shpasser\\GaeSupportL5\\Storage\\Optimizer::COMPILED_VIEWS_PATH :\n                  storage_path('framework/views'),\nEOT;\n\n        $modified = preg_replace($expression, $replacement, $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Replaced the \\'compiled\\' path in \"config/view.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Adds the GAE queue configuration to the 'config/queue.php'\n     * if it does not already exist.\n     *\n     * @param string $contents the 'config/queue.php' file contents.\n     * @return string the modified file contents.\n     */\n    protected function addQueueConfig($contents)\n    {\n        if (str_contains($contents, \"'gae'\")) {\n            return $contents;\n        }\n\n        $expression = \"/'connections'\\s*=>\\s*\\[/\";\n\n        $replacement =\n<<<EOT\n'connections' => [\n\n        'gae' => [\n            'driver'\t=> 'gae',\n            'queue'\t\t=> 'default',\n            'url'\t\t=> '/tasks',\n            'encrypt'\t=> true,\n        ],\nEOT;\n        $modified = preg_replace($expression, $replacement, $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Added queue driver configuration in \"config/queue.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Adds the Cloud SQL configuration to the 'config/database.php'\n     * if it does not already exist.\n     *\n     * @param string $contents the 'config/database.php' file contents.\n     * @return string the modified file contents.\n     */\n    protected function addCloudSqlConfig($contents)\n    {\n        if (str_contains($contents, \"'cloudsql'\")) {\n            return $contents;\n        }\n\n        $expressions = [\n            \"/'default'.*=>\\s*'\\b.+\\b'/\",\n            \"/'connections'\\s*=>\\s*\\[/\"\n        ];\n\n        $replacements = [\n            \"'default' => env('DB_CONNECTION', 'mysql')\",\n<<<EOT\n'connections' => [\n\n        'cloudsql' => [\n            'driver'      => 'mysql',\n            'unix_socket' => env('DB_SOCKET'),\n            'host'        => env('DB_HOST'),\n            'database'    => env('DB_DATABASE'),\n            'username'    => env('DB_USERNAME'),\n            'password'    => env('DB_PASSWORD'),\n            'charset'     => 'utf8',\n            'collation'   => 'utf8_unicode_ci',\n            'prefix'      => '',\n            'strict'      => false,\n        ],\nEOT\n        ];\n\n        $modified = preg_replace($expressions, $replacements, $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Added Cloud SQL driver configuration in \"config/database.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Adds the GAE disk configuration to the 'config/filesystem.php'\n     * if it does not already exist.\n     *\n     * @param string $contents the 'config/filesystem.php' file contents.\n     * @return string the modified file contents.\n     */\n    protected function addGaeDisk($contents)\n    {\n        if (str_contains($contents, \"'gae'\")) {\n            return $contents;\n        }\n\n        $expressions = [\n            \"/'default'.*=>\\s*'\\b.+\\b'/\",\n            \"/'disks'\\s*=>\\s*\\[/\"\n        ];\n\n        $replacements = [\n            \"'default' => env('FILESYSTEM', 'local')\",\n<<<EOT\n'disks' => [\n\n\t\t'gae' => [\n\t\t\t'driver' => 'gae',\n\t\t\t'root'   => storage_path().'/app',\n\t\t],\nEOT\n        ];\n\n        $modified = preg_replace($expressions, $replacements, $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Added GAE filesystem driver configuration in \"config/filesystems.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Processor function. Pre-processes windows paths.\n     *\n     * @param string $contents the 'bootstrap/cache/config.php' file contents.\n     *\n     * @return string the modified file contents.\n     */\n    protected function preprocessWindowsPaths($contents)\n    {\n        $expression = \"/'([A-Za-z]:)?((\\\\\\\\|\\/)[^\\\\/:*?\\\"\\'<>|\\r\\n]*)*'/\";\n\n        $paths = array();\n        preg_match_all($expression, $contents, $paths);\n\n        $modified = $contents;\n        foreach ($paths[0] as $path) {\n            $normalizedPath = str_replace('\\\\\\\\', '/', $path);\n            $modified = str_replace($path, $normalizedPath, $modified);\n        }\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Preprocessed windows paths in \"bootstrap/cache/config.php\".');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Fixes the paths in the cached config file.\n     *\n     * @param string $contents the 'bootstrap/cache/config.php' file contents.\n     * @return string the modified file contents.\n     */\n    protected function fixCachedConfig($contents)\n    {\n        $app_path = app_path();\n        $storage_path = storage_path();\n        $base_path = base_path();\n        $replaceFunction = 'str_replace';\n\n        if ($this->isRunningOnWindows()) {\n            $contents = $this->preprocessWindowsPaths($contents);\n            $app_path     = str_replace('\\\\', '/', $app_path);\n            $storage_path = str_replace('\\\\', '/', $storage_path);\n            $base_path    = str_replace('\\\\', '/', $base_path);\n            $replaceFunction = 'str_ireplace';\n        }\n\n        $strings = [\n            \"'${app_path}\",\n            \"'${storage_path}\",\n            \"'${base_path}\"\n        ];\n\n        $replacements = [\n            \"app_path().'\",\n            \"storage_path().'\",\n            \"base_path().'\"\n        ];\n\n        $modified = $replaceFunction($strings, $replacements, $contents);\n\n        if ($contents !== $modified) {\n            $this->myCommand->info('Generated \"bootstrap/cache/config.php\" for GAE deployment.');\n            $this->myCommand->comment('* To use \"bootstrap/cache/config.php\" locally please regenerate it.');\n        }\n\n        return $modified;\n    }\n\n    /**\n     * Generates a \"app.yaml\" file for a GAE app.\n     *\n     * @param string $appId the GAE app id.\n     * @param string $filePath the 'app.yaml' file path.\n     * @param string $publicPath the application public dir path.\n     */\n    protected function generateAppYaml($appId, $filePath, $publicPath)\n    {\n        if (file_exists($filePath)) {\n            $overwrite = $this->myCommand->confirm(\n                'Overwrite the existing \"app.yaml\" file?', false\n            );\n\n            if (! $overwrite) {\n                return;\n            }\n        }\n\n        $pathMappings = '';\n        foreach (new \\DirectoryIterator($publicPath) as $fileInfo) {\n            if ($fileInfo->isDot() || ! $fileInfo->isDir()) {\n                continue;\n            }\n\n            $dirName = $fileInfo->getFilename();\n\n            // Elixir's 'build' directory\n            // is added anyway -> skip it\n            if ($dirName === \"build\") {\n                continue;\n            }\n\n            $pathMappings .= PHP_EOL.\n<<<EOT\n        - url: /{$dirName}\n          static_dir: public/{$dirName}\n\nEOT;\n        }\n\n        $contents =\n<<<EOT\napplication:    {$appId}\nversion:        1\nruntime:        php55\napi_version:    1\n\nhandlers:\n        - url: /favicon\\.ico\n          static_files: public/favicon.ico\n          upload: public/favicon\\.ico\n\n        - url: /build\n          static_dir: public/build\n          application_readable: true\n{$pathMappings}\n        - url: /.*\n          script: public/index.php\n\nskip_files:\n        - ^(.*/)?#.*#$\n        - ^(.*/)?.*~$\n        - ^(.*/)?.*\\.py[co]$\n        - ^(.*/)?.*/RCS/.*$\n        - ^(.*/)?\\.(?!env).*$\n        - ^(.*/)?node_modules.*$\n        - ^(.*/)?_ide_helper\\.php$\n        - ^(.*/)?\\.DS_Store$\n\nenv_variables:\n        GAE_SKIP_GCS_INIT: false\nEOT;\n        file_put_contents($filePath, $contents);\n\n        $this->myCommand->info('Generated the \"app.yaml\" file.');\n    }\n\n    /**\n     * Generates a \"php.ini\" file for a GAE app.\n     *\n     * @param string $appId the GAE app id.\n     * @param string $bucketId the GAE gs-bucket id.\n     * @param string $filePath the 'php.ini' file path.\n     */\n    protected function generatePhpIni($appId, $bucketId, $filePath)\n    {\n        if (file_exists($filePath)) {\n            $overwrite = $this->myCommand->confirm(\n                'Overwrite the existing \"php.ini\" file?', false\n            );\n\n            if (! $overwrite) {\n                return;\n            }\n        }\n\n        $storageBucket = \"{$appId}.appspot.com\";\n        if ($bucketId !== null) {\n            $storageBucket = $bucketId;\n        }\n\n        $contents =\n<<<EOT\n; enable function that are disabled by default in the App Engine PHP runtime\ngoogle_app_engine.enable_functions = \"php_sapi_name, php_uname, getmypid\"\ngoogle_app_engine.allow_include_gs_buckets = \"{$storageBucket}\"\nallow_url_include = 1\nEOT;\n        file_put_contents($filePath, $contents);\n\n        $this->myCommand->info('Generated the \"php.ini\" file.');\n    }\n\n    /**\n     * Determines whether the app is running on windows.\n     * @return boolean 'true' if running on Windows,  otherwise 'false'.\n     */\n    protected function isRunningOnWindows()\n    {\n        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Creates a backup copy of a desired file.\n     *\n     * @param string $filePath the file path.\n     * @return string the created backup file path.\n     */\n    protected function backupFile($filePath)\n    {\n        $sourcePath = $filePath;\n        $backupPath = $filePath.'.bak';\n\n        if (file_exists($backupPath)) {\n            $date = new \\DateTime();\n            $backupPath = \"{$filePath}{$date->getTimestamp()}.bak\";\n        }\n\n        copy($sourcePath, $backupPath);\n\n        return $backupPath;\n    }\n\n    /**\n     * Restores a file from its backup copy.\n     *\n     * @param string $filePath the file path.\n     * @param string $backupPath the backup path.\n     * @param boolean $clean if 'true' deletes the backup copy.\n     * @return string the created backup file path.\n     */\n    protected function restoreFile($filePath, $backupPath, $clean = true)\n    {\n        if (file_exists($backupPath)) {\n            copy($backupPath, $filePath);\n\n            if ($clean) {\n                unlink($backupPath);\n            }\n        }\n\n        return $backupPath;\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Setup/EnvHelper.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nclass EnvHelper implements \\ArrayAccess\n{\n    /**\n     * ENV configuration array\n     * @var array\n     */\n    protected $lines;\n\n    /**\n     * Last loaded ENV file path.\n     *\n     * @var string\n     */\n    protected $filePath;\n\n    /**\n     * Reads a ENV file into an array.\n     *\n     * @param string $file The file path to parse.\n     */\n    public function read($file)\n    {\n        $this->filePath = $file;\n        $this->lines = file($this->filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);\n    }\n\n    /**\n     * Writes the configuration data back to the ENV file.\n     *\n     * @param string $file If not empty will be used as an\n     * ENV file path to be written.\n     */\n    public function write($file = \"\")\n    {\n        $envString = $this->generateEnvString($this->lines);\n        file_put_contents($file ? $file : $this->filePath, $envString);\n    }\n\n    /**\n     * Generates ENV string from a given associative array.\n     *\n     * @param array $array the array containing ENV data.\n     * @return string\n     */\n    protected function generateEnvString($array)\n    {\n        $envString = \"\";\n\n        foreach ($array as $value) {\n            $envString .= \"{$value}\".PHP_EOL;\n        }\n\n        return $envString;\n    }\n\n    /**\n     * Parses key and value for a given line.\n     *\n     * @param  string $line   the line to be parsed.\n     * @param  string &$key   the parsed key.\n     * @param  string &$value the parsed value.\n     * @return boolean true if parsing was successful, false otherwise.\n     */\n    protected function parseEnvLine($line, &$key, &$value)\n    {\n        $parseOk = false;\n\n        $keyExpr = '/(^\\S+)\\s*=\\s*/';\n        if (preg_match($keyExpr, $line, $matched) === 1) {\n            $key = $matched[1];\n            $value = substr($line, strlen($matched[0]));\n            $parseOk = true;\n        }\n\n        return $parseOk;\n    }\n\n    /**\n     * Finds the line containing the given key.\n     *\n     * @param  string $key the key.\n     * @return integer the line index > 0, otherwise -1.\n     */\n    protected function findLine($key)\n    {\n        $parsedKey   = null;\n        $parsedValue = null;\n\n        foreach ($this->lines as $index => $line) {\n            $parseOk = $this->parseEnvLine($line, $parsedKey, $parsedValue);\n            if ($parseOk && $key === $parsedKey) {\n                return $index;\n            }\n        }\n\n        return -1;\n    }\n\n    /**\n     * Assigns a value to the specified offset\n     *\n     * @param string $offset The offset to assign the value to\n     * @param mixed $value The value to set\n     * @access public\n     * @abstracting ArrayAccess\n     */\n    public function offsetSet($offset, $value)\n    {\n        if (is_null($offset)) {\n            $this->lines[] = $value;\n        } else {\n            $index = $this->findLine($offset);\n\n            if ($index >= 0) {\n                $this->lines[$index] = \"{$offset}={$value}\";\n            } else {\n                $this->lines[] = \"{$offset}={$value}\";\n            }\n        }\n    }\n\n    /**\n     * Whether or not an offset exists\n     *\n     * @param string $offset An offset to check for\n     * @access public\n     * @return boolean\n     * @abstracting ArrayAccess\n     */\n    public function offsetExists($offset)\n    {\n        $lineFound = false;\n\n        $index = $this->findLine($offset);\n\n        if ($index >= 0) {\n            $lineFound = true;\n        }\n\n        return $lineFound;\n    }\n\n    /**\n     * Un-sets an offset\n     *\n     * @param string $offset The offset to unset\n     * @access public\n     * @abstracting ArrayAccess\n     */\n    public function offsetUnset($offset)\n    {\n        $index = $this->findLine($offset);\n\n        if ($index >= 0) {\n            unset($this->lines[$index]);\n        }\n    }\n\n    /**\n     * Returns the value at specified offset\n     *\n     * @param string $offset The offset to retrieve\n     * @access public\n     * @return mixed\n     * @abstracting ArrayAccess\n     */\n    public function offsetGet($offset)\n    {\n        $index = $this->findLine($offset);\n\n        if ($index > 0) {\n            $parsedKey   = null;\n            $parsedValue = null;\n\n            $parseOk = $this->parseEnvLine($this->lines[$index], $parsedKey, $parsedValue);\n\n            return $parseOk ? $parsedValue : null;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Setup/IniHelper.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nclass IniHelper implements \\ArrayAccess\n{\n    /**\n     * INI configuration array\n     * @var array\n     */\n    protected $config;\n\n    /**\n     * Last loaded INI file path.\n     *\n     * @var string\n     */\n    protected $filePath;\n\n    /**\n     * Reads a INI file and parses its values into an array.\n     *\n     * @param string $file The file path to parse.\n     */\n    public function read($file)\n    {\n        $this->filePath = $file;\n        $this->config = parse_ini_file($this->filePath);\n    }\n\n    /**\n     * Writes the configuration data back to the INI file.\n     *\n     * @param string $file If not empty will be used as an\n     * INI file path to be written.\n     */\n    public function write($file = \"\")\n    {\n        $iniString = $this->generateIniString($this->config);\n        file_put_contents($file ? $file : $this->filePath, $iniString);\n    }\n\n    /**\n     * Generates an INI string from a given associative array.\n     *\n     * @param array $array the array containing the INI data.\n     * @return string\n     */\n    protected function generateIniString($array)\n    {\n        $iniString = \"\";\n\n        foreach ($array as $key => $value) {\n            if (is_array($value)) {\n                $iniString .= \"[{$key}]\".PHP_EOL;\n                $iniString .= $this->generateIniString($value);\n            } else {\n                $iniString .= \"{$key}={$value}\".PHP_EOL;\n            }\n        }\n\n        return $iniString;\n    }\n\n    /**\n     * Assigns a value to the specified offset\n     *\n     * @param string $offset The offset to assign the value to\n     * @param mixed $value The value to set\n     * @access public\n     * @abstracting ArrayAccess\n     */\n    public function offsetSet($offset, $value)\n    {\n        if (is_null($offset)) {\n            $this->config[] = $value;\n        } else {\n            $this->config[$offset] = $value;\n        }\n    }\n\n    /**\n     * Whether or not an offset exists\n     *\n     * @param string $offset An offset to check for\n     * @access public\n     * @return boolean\n     * @abstracting ArrayAccess\n     */\n    public function offsetExists($offset)\n    {\n        return isset($this->config[$offset]);\n    }\n\n    /**\n     * Un-sets an offset\n     *\n     * @param string $offset The offset to unset\n     * @access public\n     * @abstracting ArrayAccess\n     */\n    public function offsetUnset($offset)\n    {\n        unset($this->config[$offset]);\n    }\n\n    /**\n     * Returns the value at specified offset\n     *\n     * @param string $offset The offset to retrieve\n     * @access public\n     * @return mixed\n     * @abstracting ArrayAccess\n     */\n    public function offsetGet($offset)\n    {\n        return isset($this->config[$offset]) ? $this->config[$offset] : null;\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Setup/SetupCommand.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse Illuminate\\Console\\Command;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\n\nclass SetupCommand extends Command\n{\n    /**\n     * The console command name.\n     *\n     * @var string\n     */\n    protected $name = 'gae:setup';\n\n    /**\n     * The console command description.\n     *\n     * @var string\n     */\n    protected $description = 'Add Google App Engine support to the application.';\n\n    /**\n     * Create a new command instance.\n     */\n    public function __construct()\n    {\n        parent::__construct();\n    }\n\n    /**\n     * Execute the console command.\n     *\n     * @return mixed\n     */\n    public function handle()\n    {\n        $this->fire();\n    }\n\n    /**\n     * Execute the console command.\n     * For backward compatibility.\n     *\n     * @return mixed\n     */\n    public function fire()\n    {\n        $dbSocket = $this->option('db-socket');\n        $dbHost   = $this->option('db-host');\n        $dbName   = $this->option('db-name');\n\n        if (! is_null($dbName) && (is_null($dbSocket) && is_null($dbHost))) {\n            $this->error(\"Option '--db-name' requires at least one of: '--db-socket' OR '--db-host' to be defined.\");\n            return;\n        }\n\n        $configurator = new Configurator($this);\n        $configurator->configure(\n            $this->argument('app-id'),\n            $this->option('config'),\n            $this->option('cache-config'),\n            $this->option('bucket'),\n            $this->option('db-socket'),\n            $this->option('db-name'),\n            $this->option('db-host')\n        );\n    }\n\n    /**\n     * Get the console command arguments.\n     *\n     * @return array\n     */\n    protected function getArguments()\n    {\n        return array(\n            array('app-id', InputArgument::REQUIRED, 'GAE application ID.'),\n        );\n    }\n\n    /**\n     * Get the console command options.\n     *\n     * @return array\n     */\n    protected function getOptions()\n    {\n        return array(\n            array('config', null, InputOption::VALUE_NONE,\n                  'Generate \"app.yaml\" and \"php.ini\" config files.', null),\n            array('cache-config', null, InputOption::VALUE_NONE,\n                'Generate cached Laravel config file for use on Google App Engine.', null),\n            array('bucket', null, InputOption::VALUE_REQUIRED,\n                  'Use the specified gs-bucket instead of the default one.', null),\n            array('db-socket', null, InputOption::VALUE_REQUIRED,\n                  'Cloud SQL socket connection string for production environment.', null),\n            array('db-name', null, InputOption::VALUE_REQUIRED,\n                  'Cloud SQL database name.', null),\n            array('db-host', null, InputOption::VALUE_REQUIRED,\n                  'Cloud SQL database host IPv4 address for local environment.', null),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Storage/CacheFs.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Storage;\n\nuse Memcached;\n\n/**\n * A Stream Wrapper for Cache File System.\n *\n * See: http://www.php.net/manual/en/class.streamwrapper.php\n */\nfinal class CacheFs\n{\n    /**\n     * This property must be public so PHP can populate\n     * it with the actual context resource.\n     * @var resource\n     */\n    public $context;\n\n    private $fd;\n    private $mode;\n    private $path;\n\n    private static $folders = [];\n\n    private $dir;\n\n    const DIR_MODE  = 040777;\n    const FILE_MODE = 0100777;\n    const PROTOCOL  = 'cachefs';\n\n\n    /**\n     * @var Memcached\n     */\n    private static $memcached = null;\n\n    /**\n     * Register the stream wrapper only once\n     * @var boolean\n     */\n    private static $registered = false;\n\n\n    /**\n     * Establishes a connection to memcached server and\n     * registers the Stream Wrapper.\n     *\n     * @return boolean 'true' if connection was successful, 'false' otherwise.\n     */\n    public static function initialize()\n    {\n        if (self::$registered) {\n            return true;\n        }\n\n        try {\n            // initialize the connection to memcached\n            if (is_null(self::cache())) {\n                return false;\n            }\n            // register the wrapper\n            stream_wrapper_register(self::PROTOCOL, 'Shpasser\\GaeSupportL5\\Storage\\CacheFs');\n            self::$registered = true;\n        } catch (\\RuntimeException $ex) {\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns the memcached instance.\n     *\n     * @return Memcached\n     * @throws RuntimeException\n     */\n    private static function cache()\n    {\n        if (is_null(self::$memcached) && class_exists('Memcached')) {\n            $servers = [['host' => '127.0.0.1', 'port' => 11211, 'weight' => 100]];\n\n            self::$memcached = new Memcached();\n\n            foreach ($servers as $server) {\n                self::$memcached->addServer(\n                    $server['host'], $server['port'], $server['weight']\n                );\n            }\n\n            if (self::$memcached->getVersion() === false) {\n                throw new \\RuntimeException(\"Could not establish Memcached connection.\");\n            }\n        }\n\n        return self::$memcached;\n    }\n\n    private function dir_list($path)\n    {\n        $dirPath = $path.'/';\n        $length = strlen($dirPath);\n        $paths = array_merge(self::cache()->getAllKeys(), self::$folders);\n\n        $dir = array_filter($paths, function ($file) use ($dirPath,$length) {\n            if (substr($file, 0, $length) === $dirPath\n                && strrpos($file, '/') == $length) {\n                return true;\n            }\n            return false;\n        });\n\n        return $dir;\n    }\n\n    /**\n     * Constructs a new stream wrapper.\n     */\n    public function __construct()\n    {\n        $this->fd = null;\n        $this->mode = null;\n        $this->path = null;\n    }\n\n    /**\n     * Destructs an existing stream wrapper.\n     */\n    public function __destruct()\n    {\n    }\n\n    /**\n     * Renames a storage object.\n     *\n     * @return true if the object was renamed, false otherwise\n     */\n    public function rename($from, $to)\n    {\n        // TODO: add directories support\n\n        $contents = self::cache()->get($from);\n        if (false === $contents) {\n            return false;\n        }\n\n        self::cache()->delete($from);\n        self::cache()->set($to, $contents);\n\n        return true;\n    }\n\n    /**\n     * Closes the stream\n     */\n    public function stream_close()\n    {\n        $this->stream_flush();\n        fclose($this->fd);\n        $this->path = null;\n        $this->fd = null;\n    }\n\n    /**\n    * Tests for end-of-file on a file pointer.\n    *\n    * @return true if the read/write position is at the end of the stream and if\n    * no more data is available to be read, otherwise false\n    */\n    public function stream_eof()\n    {\n        return feof($this->fd);\n    }\n\n    /**\n     * Flushes the output.\n     *\n     * @return true if the cached data was successfully stored (or if there was\n     * no data to store), false if the data could not be stored.\n     */\n    public function stream_flush()\n    {\n        switch ($this->mode) {\n            case 'r+':\n            case 'rb+':\n            case 'w':\n            case 'wb':\n            case 'w+':\n            case 'wb+':\n            case 'a':\n            case 'ab':\n            case 'a+':\n            case 'ab+':\n                $origPos = ftell($this->fd);\n                fseek($this->fd, 0, SEEK_END);\n                $size = ftell($this->fd);\n                fseek($this->fd, 0, SEEK_SET);\n                $contents = fread($this->fd, $size);\n                fseek($this->fd, $origPos, SEEK_SET);\n                self::cache()->set($this->path, $contents);\n                break;\n        }\n\n        return true;\n    }\n\n    /**\n     * Stream metadata is not supported.\n     *\n     * @param $path\n     * @param $option\n     * @param $value\n     * @return bool always false.\n     */\n    public function stream_metadata($path, $option, $value)\n    {\n        return false;\n    }\n\n    /**\n     * Opens a stream.\n     *\n     * @param $path\n     * @param $mode\n     * @param $options\n     * @param $opened_path\n     * @return bool true on success, false otherwise.\n     * @throws RuntimeException\n     */\n    public function stream_open($path, $mode, $options, &$opened_path)\n    {\n        $contents = self::cache()->get($path);\n        $fileExists = (false !== $contents);\n\n        switch ($mode) {\n            case 'r':\n            case 'rb':\n            case 'r+':\n            case 'rb+':\n                if (! $fileExists) {\n                    return false;\n                }\n\n                $this->fd = fopen('php://memory', \"wb+\");\n                fwrite($this->fd, $contents);\n                fseek($this->fd, 0, SEEK_SET);\n                break;\n\n            case 'w':\n            case 'wb':\n            case 'w+':\n            case 'wb+':\n                $this->fd = fopen('php://memory', \"wb+\");\n                break;\n\n            case 'a':\n            case 'ab':\n            case 'a+':\n            case 'ab+':\n                $this->fd = fopen('php://memory', \"wb+\");\n\n                if ($fileExists) {\n                    fwrite($this->fd, $contents);\n                    fseek($this->fd, 0, SEEK_END);\n                }\n                break;\n\n            default:\n                return false;\n        }\n\n        $this->path = $path;\n        $this->mode = $mode;\n\n        return true;\n    }\n\n\n    /**\n     * Reads from a stream.\n     *\n     * @return string string of bytes.\n     */\n    public function stream_read($count)\n    {\n        return fread($this->fd, $count);\n    }\n\n    /**\n     * Performs a seek operation on a stream.\n     *\n     * @param $offset\n     * @param $whence\n     * @return int\n     */\n    public function stream_seek($offset, $whence)\n    {\n        return fseek($this->fd, $offset, $whence);\n    }\n\n    /**\n     * Stream option setting is not supported.\n     *\n     * @return bool always false.\n     */\n    public function stream_set_option($option, $arg1, $arg2)\n    {\n        return false;\n    }\n\n    /**\n     * Returns a stream stat information.\n     *\n     * @return array stat information.\n     */\n    public function stream_stat()\n    {\n        return $this->url_stat($this->path, 0);\n    }\n\n    /**\n     * Returns the current position in a stream.\n     *\n     * @return int the position.\n     */\n    public function stream_tell()\n    {\n        return ftell($this->fd);\n    }\n\n    /**\n     * Returns the number of bytes written.\n     */\n    public function stream_write($data)\n    {\n        return fwrite($this->fd, $data);\n    }\n\n    /**\n     * Deletes a file. Called in response to unlink($filename).\n     */\n    public function unlink($path)\n    {\n        if (false === self::cache()->get($path)) {\n            return false;\n        }\n\n        self::cache()->delete($path);\n\n        return true;\n    }\n\n    /**\n     * Returns stat information for a given path.\n     *\n     * @param  string $path\n     * @param  int $flags\n     * @return array stat information.\n     */\n    public function url_stat($path, $flags)\n    {\n        $now  = time();\n        $stat = [\n            'dev'    =>    0, // no specific device number\n            'ino'    =>    0, // no inode number\n            'mode'    =>    0, // inode protection mode\n            'nlink'    =>    1, // number of links\n            'uid'    =>    0, // no userid of owner\n            'gid'    =>    0, // no groupid of owner\n            'rdev'    =>    0, // no device type, not inode device\n            'size'    =>    0, // size in bytes\n            'atime'    =>    $now, // time of last access (Unix timestamp)\n            'mtime'    =>    $now, // time of last modification (Unix timestamp)\n            'ctime' =>    $now, // time of last inode change (Unix timestamp)\n            'blksize' => 512, // blocksize of filesystem IO\n            'blocks'  => 0, // number of 512-byte blocks allocated **\n        ];\n\n        if (array_has(self::$folders, $path)) {\n            $stat['mode'] = self::DIR_MODE;\n\n            return $stat;\n        }\n\n        $contents = self::cache()->get($path);\n\n        if (false === $contents) {\n            return false;\n        }\n\n        $size = strlen($contents);\n        $stat['mode']   = self::FILE_MODE;\n        $stat['size']   = $size;\n        $stat['blocks'] = (int)(($size + 512) / 512);\n\n        return $stat;\n    }\n\n    /**\n     * Closes directory listing operation.\n     *\n     * @return bool always true.\n     */\n    public function dir_closedir()\n    {\n        $this->dir = null;\n        return true;\n    }\n\n    /**\n     * Opens directory listing operation.\n     *\n     * @return bool always true.\n     */\n    public function dir_opendir($path, $options)\n    {\n        $this->dir = $this->dir_list($path);\n        return true;\n    }\n\n    /**\n     * Returns the nex element in directory listing.\n     *\n     * @return string the next directory element name.\n     */\n    public function dir_readdir()\n    {\n        return next($this->dir);\n    }\n\n    /**\n     * Restarts directory listing operation.\n     *\n     * @return bool always true.\n     */\n    public function dir_rewinddir()\n    {\n        reset($this->dir);\n        return true;\n    }\n\n    /**\n     * Creates a directory.\n     *\n     * @param string $path\n     * @param int $mode\n     * @param int $options\n     *\n     * @return boot true on success, false otherwise.\n     */\n    public function mkdir($path, $mode, $options)\n    {\n        if (array_has(self::$folders, $path)) {\n            return true;\n        }\n\n        $parentEnd = strrpos($path, '/');\n        $parent = substr($path, 0, $parentEnd);\n\n        if (array_has(self::$folders, $parent)) {\n            self::$folders[] = $path;\n            return true;\n        }\n\n        if (! ($options & STREAM_MKDIR_RECURSIVE)) {\n            return false;\n        }\n\n        while ($path !== self::PROTOCOL.':/') {\n            if (array_has(self::$folders, $path)) {\n                break;\n            } else {\n                self::$folders[] = $path;\n            }\n\n            $path = substr($path, 0, $parentEnd);\n            $parentEnd = strrpos($path, '/');\n        }\n\n        return true;\n    }\n\n    /**\n     * Removes a directory.\n     *\n     * @param string $path\n     * @param int $options\n     *\n     * @return boot true on success, false otherwise.\n     */\n    public function rmdir($path, $options)\n    {\n        $dir = $this->dir_list($path);\n\n        if (empty($dir)) {\n            $count = count(self::$folders);\n            for ($index = 0; index < $count; $index++) {\n                if (self::$folders[$index] === $path) {\n                    unset(self::$folders[$index]);\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Shpasser/GaeSupportL5/Storage/Optimizer.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Storage;\n\n/**\n * Initializes caching of Laravel 5.1 configuration files on GAE.\n */\nclass Optimizer\n{\n    const CONFIG_PATH = 'cachefs://bootstrap/cache';\n    const COMPILED_VIEWS_PATH = 'cachefs://framework/views';\n\n    /**\n     * @var boolean\n     */\n    protected $runningInConsole;\n\n    /**\n     * Configuration file paths.\n     * @var string\n     */\n    protected $configPath;\n    protected $routesPath;\n    protected $servicesPath;\n\n    /**\n     * Application base path.\n     * @var string\n     */\n    protected $basePath;\n\n    /**\n     * Keep track of cached files, cache only once.\n     * @var array\n     */\n    protected $cachedFiles;\n\n    /**\n     * @var boolean\n     */\n    protected $initialized;\n\n\n    /**\n     * Constructs an instance of GaeCacheManager.\n     *\n     * @param string $basePath Laravel base path.\n     * @param boolean $runningInConsole 'true' if running in console.\n     */\n    public function __construct($basePath, $runningInConsole)\n    {\n        $this->basePath = $basePath;\n        $this->runningInConsole = $runningInConsole;\n        $this->initialized = false;\n        $this->cachedFiles = array();\n\n        $this->configPath   = self::CONFIG_PATH.'/config.php';\n        $this->routesPath   = self::CONFIG_PATH.'/routes.php';\n        $this->servicesPath = self::CONFIG_PATH.'/services.json';\n    }\n\n\n    /**\n     * Bootstraps the Optimizer.\n     *\n     * @return boolean 'true' if successful, otherwise 'false'.\n     */\n    public function bootstrap()\n    {\n        if (! $this->runningInConsole && $this->initializeFs()) {\n            $this->buildFsTree();\n            $this->initialized = true;\n        }\n\n        return $this->initialized;\n    }\n\n\n    /**\n     * Get the path to the configuration cache file.\n     *\n     * @return string\n     */\n    public function getCachedConfigPath()\n    {\n        if ($this->initialized && env('CACHE_CONFIG_FILE')) {\n            $this->cacheFile($this->basePath.'/bootstrap/cache/config.php', $this->configPath);\n            return $this->configPath;\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Get the path to the routes cache file.\n     *\n     * @return string\n     */\n    public function getCachedRoutesPath()\n    {\n        if ($this->initialized && env('CACHE_ROUTES_FILE')) {\n            $this->cacheFile($this->basePath.'/bootstrap/cache/routes.php', $this->routesPath);\n            return $this->routesPath;\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Get the path to the cached services.json file.\n     *\n     * @return string\n     */\n    public function getCachedServicesPath()\n    {\n        return  ($this->initialized && env('CACHE_SERVICES_FILE')) ? $this->servicesPath : false;\n    }\n\n    /**\n     * Initializes the Cache Filesystem.\n     */\n    protected function initializeFs()\n    {\n        return CacheFs::initialize();\n    }\n\n    /**\n     * Builds a filesystem tree in 'cachefs'.\n     */\n    protected function buildFsTree()\n    {\n        mkdir(self::CONFIG_PATH, 0777, true);\n        mkdir(self::COMPILED_VIEWS_PATH, 0777, true);\n    }\n\n    /**\n     * Adds the requested file to cache.\n     *\n     * @param string $path path to the file to be cached.\n     * @param string $cachefsPath path for the cached file(under 'cachefs://').\n     */\n    protected function cacheFile($path, $cachefsPath)\n    {\n        if (array_key_exists($path, $this->cachedFiles)) {\n            return;\n        }\n\n        if (file_exists($path)) {\n            $contents = file_get_contents($path);\n            file_put_contents($cachefsPath, $contents);\n\n            $this->cachedFiles[$path] = $cachefsPath;\n        }\n    }\n}\n"
  },
  {
    "path": "src/views/artisan.blade.php",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Laravel Artisan Console for GAE</title>\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n        <!-- Latest compiled and minified CSS -->\n        <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css\">\n    </head>\n    <body>\n        <div class=\"container\">\n\n            <br>\n\n            <form action=\"{{ route('artisan') }}\" method=\"POST\" role=\"form\">\n                <legend>Artisan Console for Google App Engine</legend>\n\n                <input type=\"hidden\" name=\"_token\" id=\"input\" class=\"form-control\" value=\"{{ csrf_token() }}\">\n\n                <div class=\"form-group\">\n                    <label>Command</label>\n                    <input type=\"text\" name=\"command\" class=\"form-control\" placeholder=\"Use 'help' for help\" value=\"{{ $command }}\">\n                </div>\n\n                <button type=\"submit\" class=\"btn btn-primary\">Execute</button>\n            </form>\n\n            <br>\n\n            <label>Results</label>\n            <textarea name=\"results\" id=\"input\" class=\"form-control\" rows=\"20\"\n            style=\"font-family: Monaco, monospace;\" readonly=\"readonly\">{{ $results }}</textarea>\n\n        </div>\n        <!-- Latest compiled and minified JavaScript -->\n        <script src=\"//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js\"></script>\n        <script src=\"//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "tests/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Shpasser/GaeSupportL5/Setup/ConfiguratorTest.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse ZipArchive;\n\n/**\n * Class ConfiguratorTest\n *\n * @package Shpasser\\GaeSupport\\Setup\n */\nclass ConfiguratorTest extends \\PHPUnit_Framework_TestCase\n{\n    /**\n     * Helper function.\n     *\n     * Deletes a directory tree.\n     *\n     * @param string $dir the root of the directory tree to delete.\n     * @return bool 'true' if successful, 'false' otherwise.\n     */\n    protected static function delTree($dir)\n    {\n        $files = array_diff(scandir($dir), array('.', '..'));\n        foreach ($files as $file) {\n            (is_dir(\"$dir/$file\")) ? self::delTree(\"$dir/$file\") : unlink(\"$dir/$file\");\n        }\n\n        return rmdir($dir);\n    }\n\n    /**\n     * Initializes the 'testee' object and prepares,\n     * the 'playground', fake configuration files.\n     */\n    protected function setUp()\n    {\n        require_once __DIR__.'/FakeHelpers.php';\n\n        // Prepare the playground.\n        $zip = new ZipArchive();\n        $zip->open(__DIR__.'/resources.zip');\n        $zip->extractTo(__DIR__.'/playground');\n\n        // Call the configure() function on the 'testee'\n        // to generate/modify the files.\n        $configurator = new Configurator(new FakeCommand);\n\n        $appId = 'laravel-app-gae-id';\n        $generateConfig = true;\n        $cacheConfig = false;\n        $bucketId = null;\n        $dbSocket = '/cloudsql/test-app:test-cloud-sql';\n        $dbName   = 'gae_test_db';\n        $dbHost   = 'XXX.XXX.XXX.XXX';\n\n        $configurator->configure($appId, $generateConfig, $cacheConfig, $bucketId, $dbSocket, $dbName, $dbHost);\n    }\n\n    /**\n     * Cleans up the 'playground' with all of its contents.\n     */\n    protected function tearDown()\n    {\n        self::delTree(__DIR__.'/playground');\n    }\n\n    public function testEnvProductionGeneration()\n    {\n        $env_production = __DIR__.'/playground/.env.production';\n        $expected       = __DIR__.'/playground/.env.production_expected_result';\n        $this->assertFileEquals($env_production, $expected);\n    }\n\n    public function testEnvLocalGeneration()\n    {\n        $env_local = __DIR__.'/playground/.env.local';\n        $expected  = __DIR__.'/playground/.env.local_expected_result';\n        $this->assertFileEquals($env_local, $expected);\n    }\n\n    public function testBootstrapAppModification()\n    {\n        $bootstrap_app_php = __DIR__.'/playground/bootstrap/app.php';\n        $expected          = __DIR__.'/playground/bootstrap/app.php_expected_result';\n        $this->assertFileEquals($bootstrap_app_php, $expected);\n    }\n\n    public function testConfigAppModification()\n    {\n        $config_app_php = __DIR__.'/playground/config/app.php';\n        $expected       = __DIR__.'/playground/config/app.php_expected_result';\n        $this->assertFileEquals($config_app_php, $expected);\n    }\n\n    public function testConfigViewModification()\n    {\n        $config_view_php = __DIR__.'/playground/config/view.php';\n        $expected        = __DIR__.'/playground/config/view.php_expected_result';\n        $this->assertFileEquals($config_view_php, $expected);\n    }\n\n    public function testConfigQueueModification()\n    {\n        $config_queue_php = __DIR__.'/playground/config/queue.php';\n        $expected         = __DIR__.'/playground/config/queue.php_expected_result';\n        $this->assertFileEquals($config_queue_php, $expected);\n    }\n\n    public function testConfigDatabaseModification()\n    {\n        $config_queue_php = __DIR__.'/playground/config/database.php';\n        $expected         = __DIR__.'/playground/config/database.php_expected_result';\n        $this->assertFileEquals($config_queue_php, $expected);\n    }\n\n    public function testGenerateAppYaml()\n    {\n        $app_yaml   = __DIR__.'/playground/app.yaml';\n        $expected   = __DIR__.'/playground/app.yaml_expected_result';\n        $this->assertFileEquals($app_yaml, $expected);\n    }\n\n    public function testGeneratePhpIni()\n    {\n        $php_ini  = __DIR__.'/playground/php.ini';\n        $expected = __DIR__.'/playground/php.ini_expected_result';\n        $this->assertFileEquals($php_ini, $expected);\n    }\n}\n"
  },
  {
    "path": "tests/Shpasser/GaeSupportL5/Setup/FakeCommand.php",
    "content": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse Illuminate\\Console\\Command;\n\nclass FakeCommand extends Command\n{\n    protected $name = 'fake';\n    protected $description = 'Fake command for testing purposes.';\n\n    public function info($text)\n    {\n        return;\n    }\n\n    public function confirm($question, $default = true)\n    {\n        return true;\n    }\n\n    public function error($text)\n    {\n        return;\n    }\n}\n"
  },
  {
    "path": "tests/Shpasser/GaeSupportL5/Setup/FakeHelpers.php",
    "content": "<?php\n\n/**\n * Return the application path\n */\nfunction app_path()\n{\n    return __DIR__.'/playground/app';\n}\n\n/**\n * Return the storage path\n */\nfunction storage_path()\n{\n    return __DIR__.'/playground/storage';\n}\n\n/**\n * Return the base path\n */\nfunction base_path()\n{\n    return __DIR__.'/playground';\n}\n"
  }
]