Repository: shpasser/GaeSupportL5
Branch: master
Commit: 392d09f176b1
Files: 32
Total size: 92.3 KB
Directory structure:
gitextract_vpsw9dwa/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src/
│ ├── Shpasser/
│ │ └── GaeSupportL5/
│ │ ├── Filesystem/
│ │ │ └── GaeAdapter.php
│ │ ├── Foundation/
│ │ │ ├── Application.php
│ │ │ └── gae_realpath.php
│ │ ├── GaeArtisanConsoleServiceProvider.php
│ │ ├── GaeSupportServiceProvider.php
│ │ ├── Http/
│ │ │ ├── Controllers/
│ │ │ │ └── ArtisanConsoleController.php
│ │ │ └── routes.php
│ │ ├── Mail/
│ │ │ ├── GaeTransportManager.php
│ │ │ ├── MailServiceProvider.php
│ │ │ └── Transport/
│ │ │ └── GaeTransport.php
│ │ ├── Queue/
│ │ │ ├── GaeConnector.php
│ │ │ ├── GaeJob.php
│ │ │ ├── GaeQueue.php
│ │ │ ├── Listener.php
│ │ │ └── QueueServiceProvider.php
│ │ ├── Setup/
│ │ │ ├── Configurator.php
│ │ │ ├── EnvHelper.php
│ │ │ ├── IniHelper.php
│ │ │ └── SetupCommand.php
│ │ └── Storage/
│ │ ├── CacheFs.php
│ │ └── Optimizer.php
│ └── views/
│ └── artisan.blade.php
└── tests/
├── .gitkeep
└── Shpasser/
└── GaeSupportL5/
└── Setup/
├── ConfiguratorTest.php
├── FakeCommand.php
└── FakeHelpers.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/vendor
composer.phar
composer.lock
.DS_Store
================================================
FILE: .travis.yml
================================================
language: php
php:
- 5.4
- 5.5
- 5.6
- hhvm
before_script:
- travis_retry composer self-update
- travis_retry composer install --prefer-source --no-interaction --dev
script: phpunit
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Ron Shpasser <shpasser@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# GaeSupport
[](https://gitter.im/shpasser/GaeSupportL5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://packagist.org/packages/shpasser/gae-support-l5)
[](https://packagist.org/packages/shpasser/gae-support-l5)
[](https://packagist.org/packages/shpasser/gae-support-l5)
[](https://packagist.org/packages/shpasser/gae-support-l5)
Google App Engine(GAE) Support package for Laravel 5.1.
Currently supported features:
- Generation of general configuration files
- Mail service provider
- Queue service provider
- Database connection
- Filesystem
For Lumen see https://github.com/shpasser/GaeSupportLumen.
## Installation
Pull in the package via Composer.
```js
"require": {
"shpasser/gae-support-l5": "~1.0"
}
```
Then include the service provider within `config/app.php`.
```php
'providers' => [
Shpasser\GaeSupportL5\GaeSupportServiceProvider::class
];
```
## Usage
Generate the GAE related files/entries.
Command template:
```bash
php 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
```
Arguments and Options:
<pre>
php artisan gae:setup [options] [--] app-id
Arguments:
app-id GAE application ID.
Options:
--config Generate "app.yaml" and "php.ini" config files.
--cache-config Generate cached Laravel config file for use on Google App Engine.
--bucket=BUCKET Use the specified GCS-bucket instead of the default one.
--db-socket=DB-SOCKET Cloud SQL socket connection string for production environment.
--db-name=DB-NAME Cloud SQL database name.
--db-host=DB-HOST Cloud SQL database host IPv4 address for local environment.
</pre>
`--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.
`--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.
When `--db-name` option is defined at least one of `--db-socket` or `--db-host` should
be also defined.
`--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.
### Mail
The mail driver configuration can be found in `config/mail.php` and `.env.production`,
these configuration files are modified / generated by the artisan command. There is
no need in any kind of custom configuration. All the outgoing mail messages are sent
with sender address of an administrator of the application, i.e. `admin@your-app-id.appspotmail.com`.
The `sender`, `to`, `cc`, `bcc`, `replyTo`, `subject`, `body` and `attachment`
parts of email message are supported.
### Queues
The modified queue configuration file `config/queue.php` should contain:
```php
return array(
...
/*
|--------------------------------------------------------------------------
| GAE Queue Connection
|--------------------------------------------------------------------------
|
*/
'connections' => array(
'gae' => array(
'driver' => 'gae',
'queue' => 'default',
'url' => '/tasks',
'encrypt' => true,
),
...
),
);
```
The 'default' queue and encryption are used by default.
In order to use the queue your `app/Http/routes.php` file should contain the following route:
```php
Route::post('tasks', array('as' => 'tasks',
function()
{
return Queue::marshal();
}));
```
This route will be used by the GAE queue to push the jobs. Please notice that the route
and the GAE Queue Connection 'url' parameter point to the same URL. Since the requests
submitted using the route are issued by GAE itself it cannot be CSRF-protected.
For more information on the matter please see http://laravel.com/docs/5.0/queues#push-queues.
### Cache, Session and Log
Cache, Session and Log components are supported via the use of specific drivers / handlers:
- Cache - using the 'memcached' driver,
- Session - using the 'memcached' driver,
- Log - using 'syslog' handler.
The configuration options for the mentioned drivers / handlers are generated by the artisan command
and can be found in `.env.production` configuration file.
### Database
Google Cloud SQL is supported via Laravel's MySql driver. The connection configuration is added by
the artisan command to `config/database.php` under `cloudsql`. The connection parameters can be
configured using `--db-socket`, `--db-name` and `--db-host` options via the artisan command.
The database related environment variables are set in `.env.production` and `.env.local` files.
The `production` environment is configured to use the socket connection while the `local` configured
to connect via the IPv4 address of the Google Cloud SQL instance. Use Google Developers Console in
order to obtain the socket connection string and enable the IPv4 address of your database instance.
The migrations are supported while working in `local` environment only.
To use either the `production` or the `local` environment rename the appropriate file to `.env`.
### Filesystem
In order to support Laravel filesystem on GAE the artisan command modifies `config/filesystem.php`
to include an additional disk:
```php
'gae' => [
'driver' => 'gae',
'root' => storage_path().'/app',
],
```
and adds the following line to `.env.production` file:
```php
FILESYSTEM = gae
```
### Optimizations
The optimizations allow the application to reduce the use of GCS, which is the only read-write
storage available on GAE platform as of now.
In order to optimize view compilation the included `cachefs` filesystem can be used to store
compiled views using `memcached` service. `cachefs` does not provide the application with a
reliable storage solution, information stored using `memcached` is managed according to
`memcached` rules and may be deleted when `memcached` decides to. Since the views can
be compiled again without any information loss it is appropriate to store compiled
views using `cachefs`.
`cachefs` has the following structure:
<pre>
/
+-- bootstrap
+-- cache
+-- framework
+-- views
</pre>
'/framework/views' is used to store the compiled views.
Use the following option to enable the feature in `.env.production` and/or `.env.local` file:
```php
CACHE_COMPILED_VIEWS = true
```
'/bootstrap/cache' is used to store the `services.json`, `config.php` and `routes.php` files,
in order to control caching of these files use the following options in `.env.production` and/or `.env.local` file:
```php
CACHE_SERVICES_FILE = true
CACHE_CONFIG_FILE = true
CACHE_ROUTES_FILE = true
```
In order to use `config.php` first generate it using the `--cache-config` option of
`php artisan gae:setup` command. `routes.php` has to be generated using
`php artisan route:cache` command.
Cache related options are:
- supported on GAE and/or in local environment as long as `memcached` service is present,
- disabled while executing `php artisan gae:setup` command.
Additionally the initialization of GSC bucket can be skipped to boost the performance.
In order to do so, set the following option in the `app.yaml` file:
```yaml
env_variables:
GAE_SKIP_GCS_INIT: true
```
the storage path will be set to `/storage` directory of the GCS bucket and storage
directory structure creation will be skipped.
If not used the filesystem initialization can be removed to minimize GCS usage. In order to
do so, remove the following line from `.env.production` file:
```php
FILESYSTEM = gae
```
### Artisan Console for GAE
To support `artisan` commands while running on GAE the package provides `Artisan Console for GAE`.
The 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.
#### Installation
Include the service provider within `config/app.php`.
```php
'providers' => [
Shpasser\GaeSupportL5\GaeArtisanConsoleServiceProvider::class
];
```
Add `/artisan` URL handler to `app.yaml` file.
```yaml
handlers:
- url: /artisan
script: public/index.php
login: admin
secure: always
- url: /.*
script: public/index.php
```
`/artisan` URL handler has to appear before the last one (`url: /.*`), otherwise it will be ignored by GAE.
The 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.
#### Usage
Enter URL http://your-app-id.appspot.com/artisan in your browser and use the displayed form to submit `artisan` commands.
Since 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).
## Deployment
Backup the existing `.env` file if needed and rename the generated `.env.production` to `.env`
before deploying your app.
Download and install GAE SDK for PHP and deploy your app.
## Known Issues
As of now Laravel scheduled commands are not supported while running on GAE.
In order to use `Artisan Console for GAE` the application class `app/Console/Kernel`
has be edited and any of the commands scheduled using its `schedule()` function should be removed.
================================================
FILE: composer.json
================================================
{
"name": "shpasser/gae-support-l5",
"description": "Google App Engine Support for Laravel 5.1 apps.",
"license": "MIT",
"authors": [
{
"name": "Ron Shpasser",
"email": "shpasser@gmail.com"
}
],
"require": {
"php": ">=5.4.0",
"illuminate/support": "~5.0",
"league/flysystem": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.4",
"illuminate/console": "~5.0"
},
"autoload": {
"psr-4": {
"Shpasser\\": "src/Shpasser"
}
},
"minimum-stability": "stable"
}
================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
================================================
FILE: src/Shpasser/GaeSupportL5/Filesystem/GaeAdapter.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Filesystem;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Config;
/**
* Class GaeAdapter
*
* The class overrides the existing methods in order to:
*
* - remove exclusive locks(not supported by GAE) while writing files,
*
* - 'ensureDirectory()' replace a call to 'reapath()' functions with
* a call to 'gae_realpath()' function, which is compatible with GCS buckets,
*
* - 'writeStream()' replace 'fopen()' mode from 'w+', which is not supported
* on GCS buckets and replaces it with 'w', as for the specific function
* both 'w+' and 'w' should work properly.
*
* - 'applyPathPrefix()' remove trailing directory separators, which prevent
* listing of disk root directory on GAE. Originally Flysystem Local adapter
* ends up with path 'gs://bucket/storage/app//' for disk root, then 'is_dir()'
* is used to check that it is a folder path. The check fails due to the trailing
* slash which is not supported by GCS and an empty directory listing is returned.
* In order to make the check pass the path has to be 'gs://bucket/storage/app/'.
*
* @package Shpasser\GaeSupportL5\Filesystem
*/
class GaeAdapter extends Local
{
/**
* {@inheritdoc}
*/
public function __construct($root)
{
parent::__construct($root, 0, self::DISALLOW_LINKS);
}
/**
* {@inheritdoc}
*/
protected function ensureDirectory($root)
{
if (is_dir($root) === false) {
mkdir($root, 0755, true);
}
return gae_realpath($root);
}
/**
* {@inheritdoc}
*/
public function writeStream($path, $resource, Config $config)
{
$location = $this->applyPathPrefix($path);
$this->ensureDirectory(dirname($location));
if (! $stream = fopen($location, 'w')) {
return false;
}
while (! feof($resource)) {
fwrite($stream, fread($resource, 1024), 1024);
}
if (! fclose($stream)) {
return false;
}
if ($visibility = $config->get('visibility')) {
$this->setVisibility($path, $visibility);
}
return compact('path', 'visibility');
}
/**
* @inheritdoc
*/
public function applyPathPrefix($path)
{
$prefixedPath = parent::applyPathPrefix($path);
return rtrim($prefixedPath, DIRECTORY_SEPARATOR);
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Foundation/Application.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Foundation;
use Illuminate\Foundation\Application as IlluminateApplication;
use Shpasser\GaeSupportL5\Storage\Optimizer;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\CliDumper;
class Application extends IlluminateApplication
{
/**
* AppIdentityService class instantiation is done using the class
* name string so we can first check if the class exists and only then
* instantiate it.
*/
const GAE_ID_SERVICE = 'google\appengine\api\app_identity\AppIdentityService';
/**
* The GAE app ID.
*
* @var string
*/
protected $appId;
/**
* 'true' if running on GAE.
* @var boolean
*/
protected $runningOnGae;
/**
* GAE storage bucket path.
* @var string
*/
protected $gaeBucketPath;
/**
* GAE storage optimizer
*/
protected $optimizer = null;
/**
* Create a new GAE supported application instance.
*
* @param string $basePath
*/
public function __construct($basePath = null)
{
$this->gaeBucketPath = null;
// Load the 'realpath()' function replacement
// for GAE storage buckets.
require_once(__DIR__ . '/gae_realpath.php');
$this->detectGae();
if ($this->isRunningOnGae()) {
$this->replaceDefaultSymfonyLineDumpers();
}
$this->optimizer = new Optimizer($basePath, $this->runningInConsole());
$this->optimizer->bootstrap();
parent::__construct($basePath);
}
/**
* Get the path to the configuration cache file.
*
* @return string
*/
public function getCachedConfigPath()
{
$path = $this->optimizer->getCachedConfigPath();
return $path ?: parent::getCachedConfigPath();
}
/**
* Get the path to the routes cache file.
*
* @return string
*/
public function getCachedRoutesPath()
{
$path = $this->optimizer->getCachedRoutesPath();
return $path ?: parent::getCachedRoutesPath();
}
/**
* Get the path to the cached services.json file.
*
* @return string
*/
public function getCachedServicesPath()
{
$path = $this->optimizer->getCachedServicesPath();
if ($path) {
return $path;
}
if ($this->isRunningOnGae()) {
return $this->storagePath().'/framework/services.json';
}
return parent::getCachedServicesPath();
}
/**
* Detect if the application is running on GAE.
*/
protected function detectGae()
{
if (! class_exists(self::GAE_ID_SERVICE)) {
$this->runningOnGae = false;
$this->appId = null;
return;
}
$AppIdentityService = self::GAE_ID_SERVICE;
$this->appId = $AppIdentityService::getApplicationId();
$this->runningOnGae = ! preg_match('/dev~/', getenv('APPLICATION_ID'));
}
/**
* Replaces the default output stream of Symfony's
* CliDumper and HtmlDumper classes in order to
* be able to run on Google App Engine.
*
* 'php://stdout' is used by CliDumper,
* 'php://output' is used by HtmlDumper,
* both are not supported on GAE.
*/
protected function replaceDefaultSymfonyLineDumpers()
{
HtmlDumper::$defaultOutput =
CliDumper::$defaultOutput =
function ($line, $depth, $indentPad) {
if (-1 !== $depth) {
echo str_repeat($indentPad, $depth).$line.PHP_EOL;
}
};
}
/**
* Returns 'true' if running on GAE.
*
* @return bool
*/
public function isRunningOnGae()
{
return $this->runningOnGae;
}
/**
* Returns the GAE app ID.
*
* @return string
*/
public function getGaeAppId()
{
return $this->appId;
}
/**
* Override the storage path
*
* @return string Storage path URL
*/
public function storagePath()
{
if ($this->runningOnGae) {
if (! is_null($this->gaeBucketPath)) {
return $this->gaeBucketPath;
}
$buckets = ini_get('google_app_engine.allow_include_gs_buckets');
// Get the first bucket in the list.
$bucket = current(explode(', ', $buckets));
if ($bucket) {
$this->gaeBucketPath = "gs://{$bucket}/storage";
if (env('GAE_SKIP_GCS_INIT')) {
return $this->gaeBucketPath;
}
if (! file_exists($this->gaeBucketPath)) {
mkdir($this->gaeBucketPath);
mkdir($this->gaeBucketPath.'/app');
mkdir($this->gaeBucketPath.'/framework');
mkdir($this->gaeBucketPath.'/framework/views');
}
return $this->gaeBucketPath;
}
}
return parent::storagePath();
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Foundation/gae_realpath.php
================================================
<?php
/**
* GAE replacement for the original realpath() function.
*/
function gae_realpath($path)
{
$result = realpath($path);
if ($result == false) {
if (file_exists($path)) {
$result = $path;
}
}
return $result;
}
================================================
FILE: src/Shpasser/GaeSupportL5/GaeArtisanConsoleServiceProvider.php
================================================
<?php
namespace Shpasser\GaeSupportL5;
use Illuminate\Support\ServiceProvider;
class GaeArtisanConsoleServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../../views', 'gae-support-l5');
if (! $this->app->routesAreCached()) {
require __DIR__.'/Http/routes.php';
}
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('gae-artisan-console');
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/GaeSupportServiceProvider.php
================================================
<?php
namespace Shpasser\GaeSupportL5;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem as Flysystem;
use Shpasser\GaeSupportL5\Setup\SetupCommand;
use Shpasser\GaeSupportL5\Filesystem\GaeAdapter as GaeFilesystemAdapter;
class GaeSupportServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Storage::extend('gae', function ($app, $config) {
return new Flysystem(new GaeFilesystemAdapter($config['root']));
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('gae.setup', function ($app) {
return new SetupCommand;
});
$this->commands('gae.setup');
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('gae-support');
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Http/Controllers/ArtisanConsoleController.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Http\Controllers;
use Illuminate\Routing\Controller;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Illuminate\Http\Request;
use Artisan;
/**
* Artisan Console Controller for GAE.
*/
class ArtisanConsoleController extends Controller
{
const NON_INTERACTIVE = ' -n';
/**
* Shows the Artisan Console page.
* @return mixed view containing the Artisan Console.
*/
public function show()
{
$command = '';
$results = '';
return view('gae-support-l5::artisan', compact(['command', 'results']));
}
/**
* Executes a command submitted from Artisan Console page.
*
* @param Request $request Laravel HTTP request object.
* @return mixed view containing the Artisan Console.
*/
public function execute(Request $request)
{
$command = $request->input('command');
if ($command === '') {
$command = 'list';
}
$output= new BufferedOutput;
Artisan::handle(new StringInput($command.self::NON_INTERACTIVE), $output);
$results = $output->fetch();
return view('gae-support-l5::artisan', compact(['command', 'results']));
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Http/routes.php
================================================
<?php
use \Shpasser\GaeSupportL5\Http\Controllers\ArtisanConsoleController;
/**
* Maintenance routes.
*/
Route::get('artisan', array('as' => 'artisan',
'uses' => ArtisanConsoleController::class.'@show'));
Route::post('artisan', array('as' => 'artisan',
'uses' => ArtisanConsoleController::class.'@execute'));
================================================
FILE: src/Shpasser/GaeSupportL5/Mail/GaeTransportManager.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Mail;
use Illuminate\Mail\TransportManager;
use Shpasser\GaeSupportL5\Mail\Transport\GaeTransport;
class GaeTransportManager extends TransportManager
{
protected function createGaeDriver()
{
return new GaeTransport($this->app);
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Mail/MailServiceProvider.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Mail;
use Shpasser\GaeSupportL5\Mail\Transport\GaeTransport;
use Illuminate\Mail\MailServiceProvider as IlluminateMailServiceProvider;
class MailServiceProvider extends IlluminateMailServiceProvider
{
/**
* Register the Swift Transport instance.
*
* @return void
*/
protected function registerSwiftTransport()
{
if ($this->app->isRunningOnGae()) {
$this->app['swift.transport'] = $this->app->share(function ($app) {
return new GaeTransportManager($app);
});
} else {
parent::registerSwiftTransport();
}
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Mail/Transport/GaeTransport.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Mail\Transport;
use Swift_Transport;
use Swift_Mime_Message;
use Swift_Events_EventListener;
use Swift_Attachment;
use Illuminate\Support\Facades\Log;
use Shpasser\GaeSupportL5\Foundation\Application;
require_once 'google/appengine/api/mail/Message.php';
use google\appengine\api\mail\Message as GAEMessage;
class GaeTransport implements Swift_Transport
{
/**
* Application instance.
*
* @var \Shpasser\GaeSupportL5\Foundation\Application
*/
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* {@inheritdoc}
*/
public function isStarted()
{
return true;
}
/**
* {@inheritdoc}
*/
public function start()
{
return true;
}
/**
* {@inheritdoc}
*/
public function stop()
{
return true;
}
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
try {
$to = implode(', ', array_keys((array) $message->getTo()));
$cc = implode(', ', array_keys((array) $message->getCc()));
$bcc = implode(', ', array_keys((array) $message->getBcc()));
$replyto = '';
foreach ((array) $message->getReplyTo() as $address => $name) {
$replyto = $address;
break;
}
$mail_options = [
"sender" => "admin@{$this->app->getGaeAppId()}.appspotmail.com",
"to" => $to,
"subject" => $message->getSubject(),
"htmlBody" => $message->getBody()
];
if ($cc !== '') {
$mail_options['cc'] = $cc;
}
if ($bcc !== '') {
$mail_options['bcc'] = $bcc;
}
if ($replyto !== '') {
$mail_options['replyto'] = $replyto;
}
$attachments = $this->getAttachmentsArray($message);
if (count($attachments) > 0) {
$mail_options['attachments'] = $attachments;
}
$gae_message = new GAEMessage($mail_options);
$gae_message->send();
} catch (InvalidArgumentException $ex) {
Log::warning("Exception sending mail: ".$ex);
}
}
/**
* {@inheritdoc}
*/
public function registerPlugin(Swift_Events_EventListener $plugin)
{
//
}
/**
* Returns an array of attachments extracted from
* a given mail message.
*
* @param $message
*
* @return array
*/
protected function getAttachmentsArray($message)
{
$attachments = array();
foreach ($message->getChildren() as $entity) {
if ($entity instanceof Swift_Attachment) {
$attachments[] = array(
'name' => $entity->getFilename(),
'data' => $entity->getBody(),
'content_id' => "<{$entity->getContentType()}>"
);
}
}
return $attachments;
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Queue/GaeConnector.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Queue;
use Illuminate\Http\Request;
use Illuminate\Queue\Connectors\ConnectorInterface;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
class GaeConnector implements ConnectorInterface
{
/**
* The encrypter instance.
*
* @var \Illuminate\Encryption\Encrypter
*/
protected $crypt;
/**
* The current request instance.
*
* @var \Illuminate\Http\Request;
*/
protected $request;
/**
* Create a new GAE connector instance.
*
* @param \Illuminate\Contracts\Encryption\Encrypter $crypt
* @param \Illuminate\Http\Request $request
*/
public function __construct(EncrypterContract $crypt, Request $request)
{
$this->crypt = $crypt;
$this->request = $request;
}
/**
* Establish a queue connection.
*
* @param array $config
* @return \Illuminate\Queue\QueueInterface
*/
public function connect(array $config)
{
return new GaeQueue($this->request, $config['queue'], $config['url'], $config['encrypt']);
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Queue/GaeJob.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Queue;
use Illuminate\Container\Container;
use Illuminate\Queue\Jobs\Job;
use Illuminate\Contracts\Queue\Job as JobContract;
class GaeJob extends Job implements JobContract
{
/**
* The Gae queue instance.
*
* @var \Shpasser\GaeSupportL5\Queue\GaeQueue
*/
protected $gaeQueue;
/**
* The Gae message instance.
*
* @var array
*/
protected $job;
/**
* Indicates if the message was a push message.
*
* @var bool
*/
protected $pushed = false;
/**
* Create a new job instance.
*
* @param \Illuminate\Container\Container $container
* @param \Shpasser\GaeSupportL5\Queue\GaeQueue $gaeQueue
* @param object $job
* @param bool $pushed
*/
public function __construct(Container $container,
GaeQueue $gaeQueue,
$job,
$pushed = false)
{
$this->job = $job;
$this->gaeQueue = $gaeQueue;
$this->pushed = $pushed;
$this->container = $container;
}
/**
* Fire the job.
*
* @return void
*/
public function fire()
{
$this->resolveAndFire(json_decode($this->getRawBody(), true));
}
/**
* Get the raw body string for the job.
*
* @return string
*/
public function getRawBody()
{
return $this->job->body;
}
/**
* Delete the job from the queue.
*
* @return void
*/
public function delete()
{
parent::delete();
if (isset($this->job->pushed)) {
return;
}
}
/**
* Release the job back into the queue.
*
* @param int $delay
* @return void
*/
public function release($delay = 0)
{
if (! $this->pushed) {
$this->delete();
}
$this->recreateJob($delay);
}
/**
* Release a pushed job back onto the queue.
*
* @param int $delay
* @return void
*/
protected function recreateJob($delay)
{
$payload = json_decode($this->job->body, true);
array_set($payload, 'attempts', array_get($payload, 'attempts', 1) + 1);
$this->gaeQueue->recreate(json_encode($payload), $this->getQueue(), $delay);
}
/**
* Get the number of times the job has been attempted.
*
* @return int
*/
public function attempts()
{
return array_get(json_decode($this->job->body, true), 'attempts', 1);
}
/**
* Get the job identifier.
*
* @return string
*/
public function getJobId()
{
return $this->job->id;
}
/**
* Get the IoC container instance.
*
* @return \Illuminate\Container\Container
*/
public function getContainer()
{
return $this->container;
}
/**
* Get the underlying Gae queue instance.
*
* @return \Shpasser\GaeSupportL5\Queue\GaeQueue
*/
public function getGaeQueue()
{
return $this->gaeQueue;
}
/**
* Get the underlying Gae job.
*
* @return array
*/
public function getGaeJob()
{
return $this->job;
}
/**
* Get the name of the queue the job belongs to.
*
* @return string
*/
public function getQueue()
{
return array_get(json_decode($this->job->body, true), 'queue');
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Queue/GaeQueue.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Queue;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Queue\Queue;
use Illuminate\Contracts\Queue\Queue as QueueContract;
use google\appengine\api\taskqueue\PushTask;
use Log;
class GaeQueue extends Queue implements QueueContract
{
const PAYLOAD_REQ_PARAM_NAME = 'data';
/**
* The current request instance.
*
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* The name of the default tube.
*
* @var string
*/
protected $default;
/**
* URL for push.
* @var string
*/
protected $url;
/**
* Indicates if the messages should be encrypted.
*
* @var bool
*/
protected $shouldEncrypt;
/**
* Create a new Gae queue instance.
*
* @param \Illuminate\Http\Request $request
* @param string $default
* @param bool $shouldEncrypt
*/
public function __construct(Request $request, $default, $url, $shouldEncrypt = false)
{
$this->request = $request;
$this->url = $url;
$this->default = $default;
$this->shouldEncrypt = $shouldEncrypt;
}
/**
* Push a new job onto the queue.
*
* @param string $job
* @param mixed $data
* @param string $queue
* @return mixed
*/
public function push($job, $data = '', $queue = null)
{
return $this->pushRaw($this->createPayload($job, $data, $queue), $queue);
}
/**
* Push a raw payload onto the queue.
*
* @param string $payload
* @param string $queue
* @param array $options
* @return mixed
*/
public function pushRaw($payload, $queue = null, array $options = array())
{
if ($this->shouldEncrypt) {
$payload = $this->encrypter->encrypt($payload);
}
$task = new PushTask($this->url,
array(self::PAYLOAD_REQ_PARAM_NAME => $payload),
$options);
return $task->add($this->getQueue($queue));
}
/**
* Push a raw payload onto the queue after encrypting the payload.
*
* @param string $payload
* @param string $queue
* @param int $delay
* @return mixed
*/
public function recreate($payload, $queue = null, $delay)
{
$options = array('delay_seconds' => $this->getSeconds($delay));
return $this->pushRaw($payload, $queue, $options);
}
/**
* Push a new job onto the queue after a delay.
*
* @param \DateTime|int $delay
* @param string $job
* @param mixed $data
* @param string $queue
* @return mixed
*/
public function later($delay, $job, $data = '', $queue = null)
{
$delay_seconds = $this->getSeconds($delay);
$payload = $this->createPayload($job, $data, $queue);
return $this->pushRaw($payload, $queue, compact('delay_seconds'));
}
/**
* Pop the next job off of the queue.
*
* @param string $queue
* @return \Illuminate\Queue\Jobs\Job|null
*/
public function pop($queue = null)
{
throw new \RuntimeException("Pop is not supported by GaeQueue.");
}
/**
* Delete a message from the Gae queue.
*
* @param string $queue
* @param string $id
* @return void
*/
public function deleteMessage($queue, $id)
{
throw new \RuntimeException("Delete is not supported by GaeQueue.");
}
/**
* Marshal a push queue request and fire the job.
*
* @return \Illuminate\Http\Response
*/
public function marshal()
{
try {
$job = $this->marshalPushedJob();
} catch (\Exception $e) {
// Ignore for security reasons!
// So if we are being hacked
// the hacker would think it went OK.
Log::warning('Marshalling Queue Request: Invalid job.');
return new Response('OK');
}
if (isset($job->id)) {
$this->createPushedGaeJob($job)->fire();
} else {
Log::warning('Marshalling Queue Request: No GAE header supplied.');
}
return new Response('OK');
}
/**
* Marshal out the pushed job and payload.
*
* @return object
*/
protected function marshalPushedJob()
{
$r = $this->request;
$body = $this->parseJobBody($r->input(self::PAYLOAD_REQ_PARAM_NAME));
return (object) array(
'id' => $r->header('X-AppEngine-TaskName'), 'body' => $body, 'pushed' => true,
);
}
/**
* Create a new GaeJob for a pushed job.
*
* @param object $job
* @return \Shpasser\GaeSupportL5\Queue\GaeJob
*/
protected function createPushedGaeJob($job)
{
return new GaeJob($this->container, $this, $job, true);
}
/**
* Create a payload string from the given job and data.
*
* @param string $job
* @param mixed $data
* @param string $queue
* @return string
*/
protected function createPayload($job, $data = '', $queue = null)
{
$payload = $this->setMeta(parent::createPayload($job, $data), 'attempts', 1);
return $this->setMeta($payload, 'queue', $this->getQueue($queue));
}
/**
* Parse the job body for firing.
*
* @param string $body
* @return string
*/
protected function parseJobBody($body)
{
return $this->shouldEncrypt ? $this->encrypter->decrypt($body) : $body;
}
/**
* Get the queue or return the default.
*
* @param string|null $queue
* @return string
*/
public function getQueue($queue)
{
return $queue ?: $this->default;
}
/**
* Get the request instance.
*
* @return \Illuminate\Http\Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Set the request instance.
*
* @param \Illuminate\Http\Request $request
*/
public function setRequest(Request $request)
{
$this->request = $request;
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Queue/Listener.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Queue;
use Illuminate\Queue\Listener as IlluminateQueueListener;
use Symfony\Component\Process\PhpExecutableFinder;
/**
* Queue Listener class overriding the original one in order to
* prevent execution of PHP function not supported by GAE, and
* in particular 'escapeshellarg()'.
*/
class Listener extends IlluminateQueueListener
{
/**
* Build the environment specific worker command.
*
* @return string
*/
protected function buildWorkerCommand()
{
if (! app()->isRunningOnGae()) {
return parent::buildWorkerCommand();
}
$binary = (new PhpExecutableFinder)->find(false);
if (defined('HHVM_VERSION')) {
$binary .= ' --php';
}
if (defined('ARTISAN_BINARY')) {
$artisan = ARTISAN_BINARY;
} else {
$artisan = 'artisan';
}
$command = 'queue:work %s --queue=%s --delay=%s --memory=%s --sleep=%s --tries=%s';
return "{$binary} {$artisan} {$command}";
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Queue/QueueServiceProvider.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Queue;
use Illuminate\Queue\QueueServiceProvider as LaravelQueueServiceProvider;
class QueueServiceProvider extends LaravelQueueServiceProvider
{
/**
* Register the connectors on the queue manager.
*
* @param \Illuminate\Queue\QueueManager $manager
* @return void
*/
public function registerConnectors($manager)
{
parent::registerConnectors($manager);
$this->registerGaeConnector($manager);
}
/**
* Register the GAE queue connector.
*
* @param \Illuminate\Queue\QueueManager $manager
* @return void
*/
protected function registerGaeConnector($manager)
{
$app = $this->app;
$manager->addConnector('gae', function () use ($app) {
return new GaeConnector($app['encrypter'], $app['request']);
});
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Setup/Configurator.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Setup;
use Illuminate\Console\Command;
use Artisan;
use Dotenv\Dotenv;
/**
* Class Configurator
*
* @package Shpasser\GaeSupportL5\Setup
*/
class Configurator
{
protected $myCommand;
/**
* Constructs a new instance of Configurator class.
*
* @param Command $myCommand console
* command to be used for console output.
*/
public function __construct(Command $myCommand)
{
$this->myCommand = $myCommand;
}
/**
* Configures a Laravel app to be deployed on GAE.
*
* @param string $appId the GAE application ID.
* @param bool $generateConfig if 'true' => generate GAE config files(app.yaml and php.ini).
* @param bool $cacheConfig if 'true' => generate cached config file(config.php).
* @param string $bucketId the custom GCS bucket ID, if 'null' the default bucket is used.
* @param string $dbSocket Cloud SQL socket connection string.
* @param string $dbName Cloud SQL database name.
* @param string $dbHost Cloud SQL host IPv4 address.
*/
public function configure($appId, $generateConfig, $cacheConfig, $bucketId,
$dbSocket, $dbName, $dbHost)
{
$env_file = app_path().'/../.env';
$env_production_file = app_path().'/../.env.production';
$env_local_file = app_path().'/../.env.local';
$bootstrap_app_php = app_path().'/../bootstrap/app.php';
$config_app_php = app_path().'/../config/app.php';
$config_view_php = app_path().'/../config/view.php';
$config_queue_php = app_path().'/../config/queue.php';
$config_database_php = app_path().'/../config/database.php';
$config_filesystems_php = app_path().'/../config/filesystems.php';
$cached_config_php = base_path().'/bootstrap/cache/config.php';
$this->createEnvProductionFile($env_file, $env_production_file, $dbSocket, $dbName);
$this->createEnvLocalFile($env_file, $env_local_file, $dbHost, $dbName);
$this->processFile($bootstrap_app_php, ['replaceAppClass']);
$this->processFile($config_app_php, [
'replaceLaravelServiceProviders',
'setLogHandler'
]);
$this->processFile($config_view_php, ['replaceCompiledPath']);
$this->processFile($config_queue_php, ['addQueueConfig']);
$this->processFile($config_database_php, ['addCloudSqlConfig']);
$this->processFile($config_filesystems_php, ['addGaeDisk']);
if ($cacheConfig) {
$env = new Dotenv(dirname($env_production_file),
basename($env_production_file));
$env->overload();
$result = Artisan::call('config:cache', array());
if ($result === 0) {
$this->processFile($cached_config_php, ['fixCachedConfig']);
}
}
if ($generateConfig) {
$app_yaml = app_path().'/../app.yaml';
$publicPath = app_path().'/../public';
$php_ini = app_path().'/../php.ini';
$this->generateAppYaml($appId, $app_yaml, $publicPath);
$this->generatePhpIni($appId, $bucketId, $php_ini);
}
}
/**
* Creates a '.env.production' file based on the existing '.env' file.
*
* @param string $env_file The '.env' file path.
* @param string $env_production_file The '.env.production' file path.
* @param string $dbSocket Cloud SQL socket connection string.
* @param string $dbName Cloud SQL database name.
*/
protected function createEnvProductionFile($env_file, $env_production_file, $dbSocket, $dbName)
{
if (!file_exists($env_file)) {
$this->myCommand->error('Cannot find ".env" file to import the existing options.');
return;
}
if (file_exists($env_production_file)) {
$overwrite = $this->myCommand->confirm(
'Overwrite the existing ".env.production" file?', false
);
if (! $overwrite) {
return;
}
}
$env = new EnvHelper;
$env->read($env_file);
$env['APP_ENV'] = 'production';
$env['APP_DEBUG'] = 'false';
$env['APP_LOG'] = 'syslog';
$env['CACHE_DRIVER'] = 'memcached';
$env['SESSION_DRIVER'] = 'memcached';
$env['MAIL_DRIVER'] = 'gae';
$env['QUEUE_DRIVER'] = 'gae';
$env['FILESYSTEM'] = 'gae';
if ((! is_null($dbSocket)) && (! is_null($dbName))) {
$env['DB_CONNECTION'] = 'cloudsql';
$env['DB_SOCKET'] = $dbSocket;
$env['DB_HOST'] = '';
$env['DB_DATABASE'] = $dbName;
$env['DB_USERNAME'] = 'root';
$env['DB_PASSWORD'] = '';
}
$this->addOptimizerOptions($env);
$env->write($env_production_file);
$this->myCommand->info('Created the ".env.production" file.');
}
/**
* Creates a '.env.local' file based on the existing '.env' file.
*
* @param string $env_file The '.env' file path.
* @param string $env_local_file The '.env.local' file path.
* @param string $dbHost Cloud SQL host IPv4 address.
* @param string $dbName Cloud SQL database name.
*/
protected function createEnvLocalFile($env_file, $env_local_file, $dbHost, $dbName)
{
if (!file_exists($env_file)) {
$this->myCommand->error('Cannot find ".env" file to import the existing options.');
return;
}
if (file_exists($env_local_file)) {
$overwrite = $this->myCommand->confirm(
'Overwrite the existing ".env.local" file?', false
);
if (! $overwrite) {
return;
}
}
$env = new EnvHelper;
$env->read($env_file);
$env['APP_ENV'] = 'local';
$env['APP_DEBUG'] = 'true';
$env['CACHE_DRIVER'] = 'file';
$env['SESSION_DRIVER'] = 'file';
if ((! is_null($dbHost)) && (! is_null($dbName))) {
$env['DB_CONNECTION'] = 'cloudsql';
$env['DB_SOCKET'] = '';
$env['DB_HOST'] = $dbHost;
$env['DB_DATABASE'] = $dbName;
$env['DB_USERNAME'] = 'root';
$env['DB_PASSWORD'] = 'password';
}
$this->addOptimizerOptions($env);
$env->write($env_local_file);
$this->myCommand->info('Created the ".env.local" file.');
}
/**
* Adds 'Optimizer' options to an environment object.
*
* @param \Shpasser\GaeSupportL5\Setup\EnvHelper $env
* the environment object to modify.
*/
protected function addOptimizerOptions(EnvHelper $env)
{
$env['CACHE_SERVICES_FILE'] = 'false';
$env['CACHE_CONFIG_FILE'] = 'false';
$env['CACHE_ROUTES_FILE'] = 'false';
$env['CACHE_COMPILED_VIEWS'] = 'false';
}
/**
* Processes a given file with given processors.
*
* @param string $filePath the path of the file to be processed.
* @param array $processors array of processor function names to
* be called during the file processing. Every such function shall
* receive the file contents string as a parameter and return the
* modified file contents.
*
* <code>
* protected function processorFunc($contents)
* {
* ...
* return $modified;
* }
* </code>
*/
protected function processFile($filePath, $processors)
{
$contents = file_get_contents($filePath);
$processed = $contents;
foreach ($processors as $processor) {
$processed = $this->$processor($processed);
}
if ($processed === $contents) {
return;
}
$this->backupFile($filePath);
file_put_contents($filePath, $processed);
}
/**
* Processor function. Replaces the Laravel
* application class with the one compatible with GAE.
*
* @param string $contents the 'bootstrap/app.php' file contents.
*
* @return string the modified file contents.
*/
protected function replaceAppClass($contents)
{
$modified = str_replace(
'Illuminate\Foundation\Application',
'Shpasser\GaeSupportL5\Foundation\Application',
$contents);
if ($contents !== $modified) {
$this->myCommand->info('Replaced the application class in "bootstrap/app.php".');
}
return $modified;
}
/**
* Processor function. Replaces the Laravel
* service providers with GAE compatible ones.
*
* @param string $contents the 'config/app.php' file contents.
*
* @return string the modified file contents.
*/
protected function replaceLaravelServiceProviders($contents)
{
$strings = [
'Illuminate\Mail\MailServiceProvider',
'Illuminate\Queue\QueueServiceProvider'
];
// Replacement to:
// - additionally support Google App Engine Queues,
// - additionally support Google App Engine Mail.
$replacements = [
'Shpasser\GaeSupportL5\Mail\MailServiceProvider',
'Shpasser\GaeSupportL5\Queue\QueueServiceProvider'
];
$modified = str_replace($strings, $replacements, $contents);
if ($contents !== $modified) {
$this->myCommand->info('Replaced the service providers in "config/app.php".');
}
return $modified;
}
/**
* Processor function. Sets the syslog log handler
* for a Laravel GAE app.
*
* @param string $contents the 'config/app.php' file contents.
*
* @return string the modified file contents.
*/
protected function setLogHandler($contents)
{
$expression = "/'log'.*=>((?!env\('APP_LOG').)*'\b.+?\b'\)?/";
$replacement = "'log' => env('APP_LOG', 'single')";
$modified = preg_replace($expression, $replacement, $contents);
if ($contents !== $modified)
{
$this->myCommand->info('Set the log handler in "config/app.php".');
}
return $modified;
}
/**
* Processor function. Replaces 'compiled' path with GAE
* compatible one when running on GAE.
*
* @param string $contents the 'config/view.php' file contents.
* @return string the modified file contents.
*/
protected function replaceCompiledPath($contents)
{
$expression = "/'compiled'\s*=>\s*(.+(,|(?=\s+\]))|[^,]+(,|(?=\s+\])))/";
$replacement =
<<<EOT
'compiled' => env('CACHE_COMPILED_VIEWS') ?
Shpasser\GaeSupportL5\Storage\Optimizer::COMPILED_VIEWS_PATH :
storage_path('framework/views'),
EOT;
$modified = preg_replace($expression, $replacement, $contents);
if ($contents !== $modified) {
$this->myCommand->info('Replaced the \'compiled\' path in "config/view.php".');
}
return $modified;
}
/**
* Adds the GAE queue configuration to the 'config/queue.php'
* if it does not already exist.
*
* @param string $contents the 'config/queue.php' file contents.
* @return string the modified file contents.
*/
protected function addQueueConfig($contents)
{
if (str_contains($contents, "'gae'")) {
return $contents;
}
$expression = "/'connections'\s*=>\s*\[/";
$replacement =
<<<EOT
'connections' => [
'gae' => [
'driver' => 'gae',
'queue' => 'default',
'url' => '/tasks',
'encrypt' => true,
],
EOT;
$modified = preg_replace($expression, $replacement, $contents);
if ($contents !== $modified) {
$this->myCommand->info('Added queue driver configuration in "config/queue.php".');
}
return $modified;
}
/**
* Adds the Cloud SQL configuration to the 'config/database.php'
* if it does not already exist.
*
* @param string $contents the 'config/database.php' file contents.
* @return string the modified file contents.
*/
protected function addCloudSqlConfig($contents)
{
if (str_contains($contents, "'cloudsql'")) {
return $contents;
}
$expressions = [
"/'default'.*=>\s*'\b.+\b'/",
"/'connections'\s*=>\s*\[/"
];
$replacements = [
"'default' => env('DB_CONNECTION', 'mysql')",
<<<EOT
'connections' => [
'cloudsql' => [
'driver' => 'mysql',
'unix_socket' => env('DB_SOCKET'),
'host' => env('DB_HOST'),
'database' => env('DB_DATABASE'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
EOT
];
$modified = preg_replace($expressions, $replacements, $contents);
if ($contents !== $modified) {
$this->myCommand->info('Added Cloud SQL driver configuration in "config/database.php".');
}
return $modified;
}
/**
* Adds the GAE disk configuration to the 'config/filesystem.php'
* if it does not already exist.
*
* @param string $contents the 'config/filesystem.php' file contents.
* @return string the modified file contents.
*/
protected function addGaeDisk($contents)
{
if (str_contains($contents, "'gae'")) {
return $contents;
}
$expressions = [
"/'default'.*=>\s*'\b.+\b'/",
"/'disks'\s*=>\s*\[/"
];
$replacements = [
"'default' => env('FILESYSTEM', 'local')",
<<<EOT
'disks' => [
'gae' => [
'driver' => 'gae',
'root' => storage_path().'/app',
],
EOT
];
$modified = preg_replace($expressions, $replacements, $contents);
if ($contents !== $modified) {
$this->myCommand->info('Added GAE filesystem driver configuration in "config/filesystems.php".');
}
return $modified;
}
/**
* Processor function. Pre-processes windows paths.
*
* @param string $contents the 'bootstrap/cache/config.php' file contents.
*
* @return string the modified file contents.
*/
protected function preprocessWindowsPaths($contents)
{
$expression = "/'([A-Za-z]:)?((\\\\|\/)[^\\/:*?\"\'<>|\r\n]*)*'/";
$paths = array();
preg_match_all($expression, $contents, $paths);
$modified = $contents;
foreach ($paths[0] as $path) {
$normalizedPath = str_replace('\\\\', '/', $path);
$modified = str_replace($path, $normalizedPath, $modified);
}
if ($contents !== $modified) {
$this->myCommand->info('Preprocessed windows paths in "bootstrap/cache/config.php".');
}
return $modified;
}
/**
* Fixes the paths in the cached config file.
*
* @param string $contents the 'bootstrap/cache/config.php' file contents.
* @return string the modified file contents.
*/
protected function fixCachedConfig($contents)
{
$app_path = app_path();
$storage_path = storage_path();
$base_path = base_path();
$replaceFunction = 'str_replace';
if ($this->isRunningOnWindows()) {
$contents = $this->preprocessWindowsPaths($contents);
$app_path = str_replace('\\', '/', $app_path);
$storage_path = str_replace('\\', '/', $storage_path);
$base_path = str_replace('\\', '/', $base_path);
$replaceFunction = 'str_ireplace';
}
$strings = [
"'${app_path}",
"'${storage_path}",
"'${base_path}"
];
$replacements = [
"app_path().'",
"storage_path().'",
"base_path().'"
];
$modified = $replaceFunction($strings, $replacements, $contents);
if ($contents !== $modified) {
$this->myCommand->info('Generated "bootstrap/cache/config.php" for GAE deployment.');
$this->myCommand->comment('* To use "bootstrap/cache/config.php" locally please regenerate it.');
}
return $modified;
}
/**
* Generates a "app.yaml" file for a GAE app.
*
* @param string $appId the GAE app id.
* @param string $filePath the 'app.yaml' file path.
* @param string $publicPath the application public dir path.
*/
protected function generateAppYaml($appId, $filePath, $publicPath)
{
if (file_exists($filePath)) {
$overwrite = $this->myCommand->confirm(
'Overwrite the existing "app.yaml" file?', false
);
if (! $overwrite) {
return;
}
}
$pathMappings = '';
foreach (new \DirectoryIterator($publicPath) as $fileInfo) {
if ($fileInfo->isDot() || ! $fileInfo->isDir()) {
continue;
}
$dirName = $fileInfo->getFilename();
// Elixir's 'build' directory
// is added anyway -> skip it
if ($dirName === "build") {
continue;
}
$pathMappings .= PHP_EOL.
<<<EOT
- url: /{$dirName}
static_dir: public/{$dirName}
EOT;
}
$contents =
<<<EOT
application: {$appId}
version: 1
runtime: php55
api_version: 1
handlers:
- url: /favicon\.ico
static_files: public/favicon.ico
upload: public/favicon\.ico
- url: /build
static_dir: public/build
application_readable: true
{$pathMappings}
- url: /.*
script: public/index.php
skip_files:
- ^(.*/)?#.*#$
- ^(.*/)?.*~$
- ^(.*/)?.*\.py[co]$
- ^(.*/)?.*/RCS/.*$
- ^(.*/)?\.(?!env).*$
- ^(.*/)?node_modules.*$
- ^(.*/)?_ide_helper\.php$
- ^(.*/)?\.DS_Store$
env_variables:
GAE_SKIP_GCS_INIT: false
EOT;
file_put_contents($filePath, $contents);
$this->myCommand->info('Generated the "app.yaml" file.');
}
/**
* Generates a "php.ini" file for a GAE app.
*
* @param string $appId the GAE app id.
* @param string $bucketId the GAE gs-bucket id.
* @param string $filePath the 'php.ini' file path.
*/
protected function generatePhpIni($appId, $bucketId, $filePath)
{
if (file_exists($filePath)) {
$overwrite = $this->myCommand->confirm(
'Overwrite the existing "php.ini" file?', false
);
if (! $overwrite) {
return;
}
}
$storageBucket = "{$appId}.appspot.com";
if ($bucketId !== null) {
$storageBucket = $bucketId;
}
$contents =
<<<EOT
; enable function that are disabled by default in the App Engine PHP runtime
google_app_engine.enable_functions = "php_sapi_name, php_uname, getmypid"
google_app_engine.allow_include_gs_buckets = "{$storageBucket}"
allow_url_include = 1
EOT;
file_put_contents($filePath, $contents);
$this->myCommand->info('Generated the "php.ini" file.');
}
/**
* Determines whether the app is running on windows.
* @return boolean 'true' if running on Windows, otherwise 'false'.
*/
protected function isRunningOnWindows()
{
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
return true;
}
return false;
}
/**
* Creates a backup copy of a desired file.
*
* @param string $filePath the file path.
* @return string the created backup file path.
*/
protected function backupFile($filePath)
{
$sourcePath = $filePath;
$backupPath = $filePath.'.bak';
if (file_exists($backupPath)) {
$date = new \DateTime();
$backupPath = "{$filePath}{$date->getTimestamp()}.bak";
}
copy($sourcePath, $backupPath);
return $backupPath;
}
/**
* Restores a file from its backup copy.
*
* @param string $filePath the file path.
* @param string $backupPath the backup path.
* @param boolean $clean if 'true' deletes the backup copy.
* @return string the created backup file path.
*/
protected function restoreFile($filePath, $backupPath, $clean = true)
{
if (file_exists($backupPath)) {
copy($backupPath, $filePath);
if ($clean) {
unlink($backupPath);
}
}
return $backupPath;
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Setup/EnvHelper.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Setup;
class EnvHelper implements \ArrayAccess
{
/**
* ENV configuration array
* @var array
*/
protected $lines;
/**
* Last loaded ENV file path.
*
* @var string
*/
protected $filePath;
/**
* Reads a ENV file into an array.
*
* @param string $file The file path to parse.
*/
public function read($file)
{
$this->filePath = $file;
$this->lines = file($this->filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
}
/**
* Writes the configuration data back to the ENV file.
*
* @param string $file If not empty will be used as an
* ENV file path to be written.
*/
public function write($file = "")
{
$envString = $this->generateEnvString($this->lines);
file_put_contents($file ? $file : $this->filePath, $envString);
}
/**
* Generates ENV string from a given associative array.
*
* @param array $array the array containing ENV data.
* @return string
*/
protected function generateEnvString($array)
{
$envString = "";
foreach ($array as $value) {
$envString .= "{$value}".PHP_EOL;
}
return $envString;
}
/**
* Parses key and value for a given line.
*
* @param string $line the line to be parsed.
* @param string &$key the parsed key.
* @param string &$value the parsed value.
* @return boolean true if parsing was successful, false otherwise.
*/
protected function parseEnvLine($line, &$key, &$value)
{
$parseOk = false;
$keyExpr = '/(^\S+)\s*=\s*/';
if (preg_match($keyExpr, $line, $matched) === 1) {
$key = $matched[1];
$value = substr($line, strlen($matched[0]));
$parseOk = true;
}
return $parseOk;
}
/**
* Finds the line containing the given key.
*
* @param string $key the key.
* @return integer the line index > 0, otherwise -1.
*/
protected function findLine($key)
{
$parsedKey = null;
$parsedValue = null;
foreach ($this->lines as $index => $line) {
$parseOk = $this->parseEnvLine($line, $parsedKey, $parsedValue);
if ($parseOk && $key === $parsedKey) {
return $index;
}
}
return -1;
}
/**
* Assigns a value to the specified offset
*
* @param string $offset The offset to assign the value to
* @param mixed $value The value to set
* @access public
* @abstracting ArrayAccess
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->lines[] = $value;
} else {
$index = $this->findLine($offset);
if ($index >= 0) {
$this->lines[$index] = "{$offset}={$value}";
} else {
$this->lines[] = "{$offset}={$value}";
}
}
}
/**
* Whether or not an offset exists
*
* @param string $offset An offset to check for
* @access public
* @return boolean
* @abstracting ArrayAccess
*/
public function offsetExists($offset)
{
$lineFound = false;
$index = $this->findLine($offset);
if ($index >= 0) {
$lineFound = true;
}
return $lineFound;
}
/**
* Un-sets an offset
*
* @param string $offset The offset to unset
* @access public
* @abstracting ArrayAccess
*/
public function offsetUnset($offset)
{
$index = $this->findLine($offset);
if ($index >= 0) {
unset($this->lines[$index]);
}
}
/**
* Returns the value at specified offset
*
* @param string $offset The offset to retrieve
* @access public
* @return mixed
* @abstracting ArrayAccess
*/
public function offsetGet($offset)
{
$index = $this->findLine($offset);
if ($index > 0) {
$parsedKey = null;
$parsedValue = null;
$parseOk = $this->parseEnvLine($this->lines[$index], $parsedKey, $parsedValue);
return $parseOk ? $parsedValue : null;
}
return null;
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Setup/IniHelper.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Setup;
class IniHelper implements \ArrayAccess
{
/**
* INI configuration array
* @var array
*/
protected $config;
/**
* Last loaded INI file path.
*
* @var string
*/
protected $filePath;
/**
* Reads a INI file and parses its values into an array.
*
* @param string $file The file path to parse.
*/
public function read($file)
{
$this->filePath = $file;
$this->config = parse_ini_file($this->filePath);
}
/**
* Writes the configuration data back to the INI file.
*
* @param string $file If not empty will be used as an
* INI file path to be written.
*/
public function write($file = "")
{
$iniString = $this->generateIniString($this->config);
file_put_contents($file ? $file : $this->filePath, $iniString);
}
/**
* Generates an INI string from a given associative array.
*
* @param array $array the array containing the INI data.
* @return string
*/
protected function generateIniString($array)
{
$iniString = "";
foreach ($array as $key => $value) {
if (is_array($value)) {
$iniString .= "[{$key}]".PHP_EOL;
$iniString .= $this->generateIniString($value);
} else {
$iniString .= "{$key}={$value}".PHP_EOL;
}
}
return $iniString;
}
/**
* Assigns a value to the specified offset
*
* @param string $offset The offset to assign the value to
* @param mixed $value The value to set
* @access public
* @abstracting ArrayAccess
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->config[] = $value;
} else {
$this->config[$offset] = $value;
}
}
/**
* Whether or not an offset exists
*
* @param string $offset An offset to check for
* @access public
* @return boolean
* @abstracting ArrayAccess
*/
public function offsetExists($offset)
{
return isset($this->config[$offset]);
}
/**
* Un-sets an offset
*
* @param string $offset The offset to unset
* @access public
* @abstracting ArrayAccess
*/
public function offsetUnset($offset)
{
unset($this->config[$offset]);
}
/**
* Returns the value at specified offset
*
* @param string $offset The offset to retrieve
* @access public
* @return mixed
* @abstracting ArrayAccess
*/
public function offsetGet($offset)
{
return isset($this->config[$offset]) ? $this->config[$offset] : null;
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Setup/SetupCommand.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Setup;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class SetupCommand extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'gae:setup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Add Google App Engine support to the application.';
/**
* Create a new command instance.
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->fire();
}
/**
* Execute the console command.
* For backward compatibility.
*
* @return mixed
*/
public function fire()
{
$dbSocket = $this->option('db-socket');
$dbHost = $this->option('db-host');
$dbName = $this->option('db-name');
if (! is_null($dbName) && (is_null($dbSocket) && is_null($dbHost))) {
$this->error("Option '--db-name' requires at least one of: '--db-socket' OR '--db-host' to be defined.");
return;
}
$configurator = new Configurator($this);
$configurator->configure(
$this->argument('app-id'),
$this->option('config'),
$this->option('cache-config'),
$this->option('bucket'),
$this->option('db-socket'),
$this->option('db-name'),
$this->option('db-host')
);
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return array(
array('app-id', InputArgument::REQUIRED, 'GAE application ID.'),
);
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('config', null, InputOption::VALUE_NONE,
'Generate "app.yaml" and "php.ini" config files.', null),
array('cache-config', null, InputOption::VALUE_NONE,
'Generate cached Laravel config file for use on Google App Engine.', null),
array('bucket', null, InputOption::VALUE_REQUIRED,
'Use the specified gs-bucket instead of the default one.', null),
array('db-socket', null, InputOption::VALUE_REQUIRED,
'Cloud SQL socket connection string for production environment.', null),
array('db-name', null, InputOption::VALUE_REQUIRED,
'Cloud SQL database name.', null),
array('db-host', null, InputOption::VALUE_REQUIRED,
'Cloud SQL database host IPv4 address for local environment.', null),
);
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Storage/CacheFs.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Storage;
use Memcached;
/**
* A Stream Wrapper for Cache File System.
*
* See: http://www.php.net/manual/en/class.streamwrapper.php
*/
final class CacheFs
{
/**
* This property must be public so PHP can populate
* it with the actual context resource.
* @var resource
*/
public $context;
private $fd;
private $mode;
private $path;
private static $folders = [];
private $dir;
const DIR_MODE = 040777;
const FILE_MODE = 0100777;
const PROTOCOL = 'cachefs';
/**
* @var Memcached
*/
private static $memcached = null;
/**
* Register the stream wrapper only once
* @var boolean
*/
private static $registered = false;
/**
* Establishes a connection to memcached server and
* registers the Stream Wrapper.
*
* @return boolean 'true' if connection was successful, 'false' otherwise.
*/
public static function initialize()
{
if (self::$registered) {
return true;
}
try {
// initialize the connection to memcached
if (is_null(self::cache())) {
return false;
}
// register the wrapper
stream_wrapper_register(self::PROTOCOL, 'Shpasser\GaeSupportL5\Storage\CacheFs');
self::$registered = true;
} catch (\RuntimeException $ex) {
return false;
}
return true;
}
/**
* Returns the memcached instance.
*
* @return Memcached
* @throws RuntimeException
*/
private static function cache()
{
if (is_null(self::$memcached) && class_exists('Memcached')) {
$servers = [['host' => '127.0.0.1', 'port' => 11211, 'weight' => 100]];
self::$memcached = new Memcached();
foreach ($servers as $server) {
self::$memcached->addServer(
$server['host'], $server['port'], $server['weight']
);
}
if (self::$memcached->getVersion() === false) {
throw new \RuntimeException("Could not establish Memcached connection.");
}
}
return self::$memcached;
}
private function dir_list($path)
{
$dirPath = $path.'/';
$length = strlen($dirPath);
$paths = array_merge(self::cache()->getAllKeys(), self::$folders);
$dir = array_filter($paths, function ($file) use ($dirPath,$length) {
if (substr($file, 0, $length) === $dirPath
&& strrpos($file, '/') == $length) {
return true;
}
return false;
});
return $dir;
}
/**
* Constructs a new stream wrapper.
*/
public function __construct()
{
$this->fd = null;
$this->mode = null;
$this->path = null;
}
/**
* Destructs an existing stream wrapper.
*/
public function __destruct()
{
}
/**
* Renames a storage object.
*
* @return true if the object was renamed, false otherwise
*/
public function rename($from, $to)
{
// TODO: add directories support
$contents = self::cache()->get($from);
if (false === $contents) {
return false;
}
self::cache()->delete($from);
self::cache()->set($to, $contents);
return true;
}
/**
* Closes the stream
*/
public function stream_close()
{
$this->stream_flush();
fclose($this->fd);
$this->path = null;
$this->fd = null;
}
/**
* Tests for end-of-file on a file pointer.
*
* @return true if the read/write position is at the end of the stream and if
* no more data is available to be read, otherwise false
*/
public function stream_eof()
{
return feof($this->fd);
}
/**
* Flushes the output.
*
* @return true if the cached data was successfully stored (or if there was
* no data to store), false if the data could not be stored.
*/
public function stream_flush()
{
switch ($this->mode) {
case 'r+':
case 'rb+':
case 'w':
case 'wb':
case 'w+':
case 'wb+':
case 'a':
case 'ab':
case 'a+':
case 'ab+':
$origPos = ftell($this->fd);
fseek($this->fd, 0, SEEK_END);
$size = ftell($this->fd);
fseek($this->fd, 0, SEEK_SET);
$contents = fread($this->fd, $size);
fseek($this->fd, $origPos, SEEK_SET);
self::cache()->set($this->path, $contents);
break;
}
return true;
}
/**
* Stream metadata is not supported.
*
* @param $path
* @param $option
* @param $value
* @return bool always false.
*/
public function stream_metadata($path, $option, $value)
{
return false;
}
/**
* Opens a stream.
*
* @param $path
* @param $mode
* @param $options
* @param $opened_path
* @return bool true on success, false otherwise.
* @throws RuntimeException
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
$contents = self::cache()->get($path);
$fileExists = (false !== $contents);
switch ($mode) {
case 'r':
case 'rb':
case 'r+':
case 'rb+':
if (! $fileExists) {
return false;
}
$this->fd = fopen('php://memory', "wb+");
fwrite($this->fd, $contents);
fseek($this->fd, 0, SEEK_SET);
break;
case 'w':
case 'wb':
case 'w+':
case 'wb+':
$this->fd = fopen('php://memory', "wb+");
break;
case 'a':
case 'ab':
case 'a+':
case 'ab+':
$this->fd = fopen('php://memory', "wb+");
if ($fileExists) {
fwrite($this->fd, $contents);
fseek($this->fd, 0, SEEK_END);
}
break;
default:
return false;
}
$this->path = $path;
$this->mode = $mode;
return true;
}
/**
* Reads from a stream.
*
* @return string string of bytes.
*/
public function stream_read($count)
{
return fread($this->fd, $count);
}
/**
* Performs a seek operation on a stream.
*
* @param $offset
* @param $whence
* @return int
*/
public function stream_seek($offset, $whence)
{
return fseek($this->fd, $offset, $whence);
}
/**
* Stream option setting is not supported.
*
* @return bool always false.
*/
public function stream_set_option($option, $arg1, $arg2)
{
return false;
}
/**
* Returns a stream stat information.
*
* @return array stat information.
*/
public function stream_stat()
{
return $this->url_stat($this->path, 0);
}
/**
* Returns the current position in a stream.
*
* @return int the position.
*/
public function stream_tell()
{
return ftell($this->fd);
}
/**
* Returns the number of bytes written.
*/
public function stream_write($data)
{
return fwrite($this->fd, $data);
}
/**
* Deletes a file. Called in response to unlink($filename).
*/
public function unlink($path)
{
if (false === self::cache()->get($path)) {
return false;
}
self::cache()->delete($path);
return true;
}
/**
* Returns stat information for a given path.
*
* @param string $path
* @param int $flags
* @return array stat information.
*/
public function url_stat($path, $flags)
{
$now = time();
$stat = [
'dev' => 0, // no specific device number
'ino' => 0, // no inode number
'mode' => 0, // inode protection mode
'nlink' => 1, // number of links
'uid' => 0, // no userid of owner
'gid' => 0, // no groupid of owner
'rdev' => 0, // no device type, not inode device
'size' => 0, // size in bytes
'atime' => $now, // time of last access (Unix timestamp)
'mtime' => $now, // time of last modification (Unix timestamp)
'ctime' => $now, // time of last inode change (Unix timestamp)
'blksize' => 512, // blocksize of filesystem IO
'blocks' => 0, // number of 512-byte blocks allocated **
];
if (array_has(self::$folders, $path)) {
$stat['mode'] = self::DIR_MODE;
return $stat;
}
$contents = self::cache()->get($path);
if (false === $contents) {
return false;
}
$size = strlen($contents);
$stat['mode'] = self::FILE_MODE;
$stat['size'] = $size;
$stat['blocks'] = (int)(($size + 512) / 512);
return $stat;
}
/**
* Closes directory listing operation.
*
* @return bool always true.
*/
public function dir_closedir()
{
$this->dir = null;
return true;
}
/**
* Opens directory listing operation.
*
* @return bool always true.
*/
public function dir_opendir($path, $options)
{
$this->dir = $this->dir_list($path);
return true;
}
/**
* Returns the nex element in directory listing.
*
* @return string the next directory element name.
*/
public function dir_readdir()
{
return next($this->dir);
}
/**
* Restarts directory listing operation.
*
* @return bool always true.
*/
public function dir_rewinddir()
{
reset($this->dir);
return true;
}
/**
* Creates a directory.
*
* @param string $path
* @param int $mode
* @param int $options
*
* @return boot true on success, false otherwise.
*/
public function mkdir($path, $mode, $options)
{
if (array_has(self::$folders, $path)) {
return true;
}
$parentEnd = strrpos($path, '/');
$parent = substr($path, 0, $parentEnd);
if (array_has(self::$folders, $parent)) {
self::$folders[] = $path;
return true;
}
if (! ($options & STREAM_MKDIR_RECURSIVE)) {
return false;
}
while ($path !== self::PROTOCOL.':/') {
if (array_has(self::$folders, $path)) {
break;
} else {
self::$folders[] = $path;
}
$path = substr($path, 0, $parentEnd);
$parentEnd = strrpos($path, '/');
}
return true;
}
/**
* Removes a directory.
*
* @param string $path
* @param int $options
*
* @return boot true on success, false otherwise.
*/
public function rmdir($path, $options)
{
$dir = $this->dir_list($path);
if (empty($dir)) {
$count = count(self::$folders);
for ($index = 0; index < $count; $index++) {
if (self::$folders[$index] === $path) {
unset(self::$folders[$index]);
return true;
}
}
}
return false;
}
}
================================================
FILE: src/Shpasser/GaeSupportL5/Storage/Optimizer.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Storage;
/**
* Initializes caching of Laravel 5.1 configuration files on GAE.
*/
class Optimizer
{
const CONFIG_PATH = 'cachefs://bootstrap/cache';
const COMPILED_VIEWS_PATH = 'cachefs://framework/views';
/**
* @var boolean
*/
protected $runningInConsole;
/**
* Configuration file paths.
* @var string
*/
protected $configPath;
protected $routesPath;
protected $servicesPath;
/**
* Application base path.
* @var string
*/
protected $basePath;
/**
* Keep track of cached files, cache only once.
* @var array
*/
protected $cachedFiles;
/**
* @var boolean
*/
protected $initialized;
/**
* Constructs an instance of GaeCacheManager.
*
* @param string $basePath Laravel base path.
* @param boolean $runningInConsole 'true' if running in console.
*/
public function __construct($basePath, $runningInConsole)
{
$this->basePath = $basePath;
$this->runningInConsole = $runningInConsole;
$this->initialized = false;
$this->cachedFiles = array();
$this->configPath = self::CONFIG_PATH.'/config.php';
$this->routesPath = self::CONFIG_PATH.'/routes.php';
$this->servicesPath = self::CONFIG_PATH.'/services.json';
}
/**
* Bootstraps the Optimizer.
*
* @return boolean 'true' if successful, otherwise 'false'.
*/
public function bootstrap()
{
if (! $this->runningInConsole && $this->initializeFs()) {
$this->buildFsTree();
$this->initialized = true;
}
return $this->initialized;
}
/**
* Get the path to the configuration cache file.
*
* @return string
*/
public function getCachedConfigPath()
{
if ($this->initialized && env('CACHE_CONFIG_FILE')) {
$this->cacheFile($this->basePath.'/bootstrap/cache/config.php', $this->configPath);
return $this->configPath;
}
return false;
}
/**
* Get the path to the routes cache file.
*
* @return string
*/
public function getCachedRoutesPath()
{
if ($this->initialized && env('CACHE_ROUTES_FILE')) {
$this->cacheFile($this->basePath.'/bootstrap/cache/routes.php', $this->routesPath);
return $this->routesPath;
}
return false;
}
/**
* Get the path to the cached services.json file.
*
* @return string
*/
public function getCachedServicesPath()
{
return ($this->initialized && env('CACHE_SERVICES_FILE')) ? $this->servicesPath : false;
}
/**
* Initializes the Cache Filesystem.
*/
protected function initializeFs()
{
return CacheFs::initialize();
}
/**
* Builds a filesystem tree in 'cachefs'.
*/
protected function buildFsTree()
{
mkdir(self::CONFIG_PATH, 0777, true);
mkdir(self::COMPILED_VIEWS_PATH, 0777, true);
}
/**
* Adds the requested file to cache.
*
* @param string $path path to the file to be cached.
* @param string $cachefsPath path for the cached file(under 'cachefs://').
*/
protected function cacheFile($path, $cachefsPath)
{
if (array_key_exists($path, $this->cachedFiles)) {
return;
}
if (file_exists($path)) {
$contents = file_get_contents($path);
file_put_contents($cachefsPath, $contents);
$this->cachedFiles[$path] = $cachefsPath;
}
}
}
================================================
FILE: src/views/artisan.blade.php
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Laravel Artisan Console for GAE</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<br>
<form action="{{ route('artisan') }}" method="POST" role="form">
<legend>Artisan Console for Google App Engine</legend>
<input type="hidden" name="_token" id="input" class="form-control" value="{{ csrf_token() }}">
<div class="form-group">
<label>Command</label>
<input type="text" name="command" class="form-control" placeholder="Use 'help' for help" value="{{ $command }}">
</div>
<button type="submit" class="btn btn-primary">Execute</button>
</form>
<br>
<label>Results</label>
<textarea name="results" id="input" class="form-control" rows="20"
style="font-family: Monaco, monospace;" readonly="readonly">{{ $results }}</textarea>
</div>
<!-- Latest compiled and minified JavaScript -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</body>
</html>
================================================
FILE: tests/.gitkeep
================================================
================================================
FILE: tests/Shpasser/GaeSupportL5/Setup/ConfiguratorTest.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Setup;
use ZipArchive;
/**
* Class ConfiguratorTest
*
* @package Shpasser\GaeSupport\Setup
*/
class ConfiguratorTest extends \PHPUnit_Framework_TestCase
{
/**
* Helper function.
*
* Deletes a directory tree.
*
* @param string $dir the root of the directory tree to delete.
* @return bool 'true' if successful, 'false' otherwise.
*/
protected static function delTree($dir)
{
$files = array_diff(scandir($dir), array('.', '..'));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? self::delTree("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}
/**
* Initializes the 'testee' object and prepares,
* the 'playground', fake configuration files.
*/
protected function setUp()
{
require_once __DIR__.'/FakeHelpers.php';
// Prepare the playground.
$zip = new ZipArchive();
$zip->open(__DIR__.'/resources.zip');
$zip->extractTo(__DIR__.'/playground');
// Call the configure() function on the 'testee'
// to generate/modify the files.
$configurator = new Configurator(new FakeCommand);
$appId = 'laravel-app-gae-id';
$generateConfig = true;
$cacheConfig = false;
$bucketId = null;
$dbSocket = '/cloudsql/test-app:test-cloud-sql';
$dbName = 'gae_test_db';
$dbHost = 'XXX.XXX.XXX.XXX';
$configurator->configure($appId, $generateConfig, $cacheConfig, $bucketId, $dbSocket, $dbName, $dbHost);
}
/**
* Cleans up the 'playground' with all of its contents.
*/
protected function tearDown()
{
self::delTree(__DIR__.'/playground');
}
public function testEnvProductionGeneration()
{
$env_production = __DIR__.'/playground/.env.production';
$expected = __DIR__.'/playground/.env.production_expected_result';
$this->assertFileEquals($env_production, $expected);
}
public function testEnvLocalGeneration()
{
$env_local = __DIR__.'/playground/.env.local';
$expected = __DIR__.'/playground/.env.local_expected_result';
$this->assertFileEquals($env_local, $expected);
}
public function testBootstrapAppModification()
{
$bootstrap_app_php = __DIR__.'/playground/bootstrap/app.php';
$expected = __DIR__.'/playground/bootstrap/app.php_expected_result';
$this->assertFileEquals($bootstrap_app_php, $expected);
}
public function testConfigAppModification()
{
$config_app_php = __DIR__.'/playground/config/app.php';
$expected = __DIR__.'/playground/config/app.php_expected_result';
$this->assertFileEquals($config_app_php, $expected);
}
public function testConfigViewModification()
{
$config_view_php = __DIR__.'/playground/config/view.php';
$expected = __DIR__.'/playground/config/view.php_expected_result';
$this->assertFileEquals($config_view_php, $expected);
}
public function testConfigQueueModification()
{
$config_queue_php = __DIR__.'/playground/config/queue.php';
$expected = __DIR__.'/playground/config/queue.php_expected_result';
$this->assertFileEquals($config_queue_php, $expected);
}
public function testConfigDatabaseModification()
{
$config_queue_php = __DIR__.'/playground/config/database.php';
$expected = __DIR__.'/playground/config/database.php_expected_result';
$this->assertFileEquals($config_queue_php, $expected);
}
public function testGenerateAppYaml()
{
$app_yaml = __DIR__.'/playground/app.yaml';
$expected = __DIR__.'/playground/app.yaml_expected_result';
$this->assertFileEquals($app_yaml, $expected);
}
public function testGeneratePhpIni()
{
$php_ini = __DIR__.'/playground/php.ini';
$expected = __DIR__.'/playground/php.ini_expected_result';
$this->assertFileEquals($php_ini, $expected);
}
}
================================================
FILE: tests/Shpasser/GaeSupportL5/Setup/FakeCommand.php
================================================
<?php
namespace Shpasser\GaeSupportL5\Setup;
use Illuminate\Console\Command;
class FakeCommand extends Command
{
protected $name = 'fake';
protected $description = 'Fake command for testing purposes.';
public function info($text)
{
return;
}
public function confirm($question, $default = true)
{
return true;
}
public function error($text)
{
return;
}
}
================================================
FILE: tests/Shpasser/GaeSupportL5/Setup/FakeHelpers.php
================================================
<?php
/**
* Return the application path
*/
function app_path()
{
return __DIR__.'/playground/app';
}
/**
* Return the storage path
*/
function storage_path()
{
return __DIR__.'/playground/storage';
}
/**
* Return the base path
*/
function base_path()
{
return __DIR__.'/playground';
}
gitextract_vpsw9dwa/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src/
│ ├── Shpasser/
│ │ └── GaeSupportL5/
│ │ ├── Filesystem/
│ │ │ └── GaeAdapter.php
│ │ ├── Foundation/
│ │ │ ├── Application.php
│ │ │ └── gae_realpath.php
│ │ ├── GaeArtisanConsoleServiceProvider.php
│ │ ├── GaeSupportServiceProvider.php
│ │ ├── Http/
│ │ │ ├── Controllers/
│ │ │ │ └── ArtisanConsoleController.php
│ │ │ └── routes.php
│ │ ├── Mail/
│ │ │ ├── GaeTransportManager.php
│ │ │ ├── MailServiceProvider.php
│ │ │ └── Transport/
│ │ │ └── GaeTransport.php
│ │ ├── Queue/
│ │ │ ├── GaeConnector.php
│ │ │ ├── GaeJob.php
│ │ │ ├── GaeQueue.php
│ │ │ ├── Listener.php
│ │ │ └── QueueServiceProvider.php
│ │ ├── Setup/
│ │ │ ├── Configurator.php
│ │ │ ├── EnvHelper.php
│ │ │ ├── IniHelper.php
│ │ │ └── SetupCommand.php
│ │ └── Storage/
│ │ ├── CacheFs.php
│ │ └── Optimizer.php
│ └── views/
│ └── artisan.blade.php
└── tests/
├── .gitkeep
└── Shpasser/
└── GaeSupportL5/
└── Setup/
├── ConfiguratorTest.php
├── FakeCommand.php
└── FakeHelpers.php
SYMBOL INDEX (176 symbols across 23 files)
FILE: src/Shpasser/GaeSupportL5/Filesystem/GaeAdapter.php
class GaeAdapter (line 31) | class GaeAdapter extends Local
method __construct (line 36) | public function __construct($root)
method ensureDirectory (line 44) | protected function ensureDirectory($root)
method writeStream (line 56) | public function writeStream($path, $resource, Config $config)
method applyPathPrefix (line 83) | public function applyPathPrefix($path)
FILE: src/Shpasser/GaeSupportL5/Foundation/Application.php
class Application (line 10) | class Application extends IlluminateApplication
method __construct (line 49) | public function __construct($basePath = null)
method getCachedConfigPath (line 75) | public function getCachedConfigPath()
method getCachedRoutesPath (line 88) | public function getCachedRoutesPath()
method getCachedServicesPath (line 100) | public function getCachedServicesPath()
method detectGae (line 119) | protected function detectGae()
method replaceDefaultSymfonyLineDumpers (line 142) | protected function replaceDefaultSymfonyLineDumpers()
method isRunningOnGae (line 158) | public function isRunningOnGae()
method getGaeAppId (line 168) | public function getGaeAppId()
method storagePath (line 178) | public function storagePath()
FILE: src/Shpasser/GaeSupportL5/Foundation/gae_realpath.php
function gae_realpath (line 6) | function gae_realpath($path)
FILE: src/Shpasser/GaeSupportL5/GaeArtisanConsoleServiceProvider.php
class GaeArtisanConsoleServiceProvider (line 7) | class GaeArtisanConsoleServiceProvider extends ServiceProvider
method boot (line 21) | public function boot()
method register (line 35) | public function register()
method provides (line 45) | public function provides()
FILE: src/Shpasser/GaeSupportL5/GaeSupportServiceProvider.php
class GaeSupportServiceProvider (line 11) | class GaeSupportServiceProvider extends ServiceProvider
method boot (line 25) | public function boot()
method register (line 37) | public function register()
method provides (line 51) | public function provides()
FILE: src/Shpasser/GaeSupportL5/Http/Controllers/ArtisanConsoleController.php
class ArtisanConsoleController (line 14) | class ArtisanConsoleController extends Controller
method show (line 22) | public function show()
method execute (line 35) | public function execute(Request $request)
FILE: src/Shpasser/GaeSupportL5/Mail/GaeTransportManager.php
class GaeTransportManager (line 8) | class GaeTransportManager extends TransportManager
method createGaeDriver (line 10) | protected function createGaeDriver()
FILE: src/Shpasser/GaeSupportL5/Mail/MailServiceProvider.php
class MailServiceProvider (line 8) | class MailServiceProvider extends IlluminateMailServiceProvider
method registerSwiftTransport (line 15) | protected function registerSwiftTransport()
FILE: src/Shpasser/GaeSupportL5/Mail/Transport/GaeTransport.php
class GaeTransport (line 15) | class GaeTransport implements Swift_Transport
method __construct (line 24) | public function __construct(Application $app)
method isStarted (line 32) | public function isStarted()
method start (line 40) | public function start()
method stop (line 48) | public function stop()
method send (line 56) | public function send(Swift_Mime_Message $message, &$failedRecipients =...
method registerPlugin (line 103) | public function registerPlugin(Swift_Events_EventListener $plugin)
method getAttachmentsArray (line 116) | protected function getAttachmentsArray($message)
FILE: src/Shpasser/GaeSupportL5/Queue/GaeConnector.php
class GaeConnector (line 9) | class GaeConnector implements ConnectorInterface
method __construct (line 32) | public function __construct(EncrypterContract $crypt, Request $request)
method connect (line 44) | public function connect(array $config)
FILE: src/Shpasser/GaeSupportL5/Queue/GaeJob.php
class GaeJob (line 9) | class GaeJob extends Job implements JobContract
method __construct (line 40) | public function __construct(Container $container,
method fire (line 56) | public function fire()
method getRawBody (line 66) | public function getRawBody()
method delete (line 76) | public function delete()
method release (line 91) | public function release($delay = 0)
method recreateJob (line 106) | protected function recreateJob($delay)
method attempts (line 120) | public function attempts()
method getJobId (line 130) | public function getJobId()
method getContainer (line 140) | public function getContainer()
method getGaeQueue (line 150) | public function getGaeQueue()
method getGaeJob (line 160) | public function getGaeJob()
method getQueue (line 170) | public function getQueue()
FILE: src/Shpasser/GaeSupportL5/Queue/GaeQueue.php
class GaeQueue (line 12) | class GaeQueue extends Queue implements QueueContract
method __construct (line 50) | public function __construct(Request $request, $default, $url, $shouldE...
method push (line 66) | public function push($job, $data = '', $queue = null)
method pushRaw (line 79) | public function pushRaw($payload, $queue = null, array $options = arra...
method recreate (line 99) | public function recreate($payload, $queue = null, $delay)
method later (line 115) | public function later($delay, $job, $data = '', $queue = null)
method pop (line 130) | public function pop($queue = null)
method deleteMessage (line 142) | public function deleteMessage($queue, $id)
method marshal (line 152) | public function marshal()
method marshalPushedJob (line 178) | protected function marshalPushedJob()
method createPushedGaeJob (line 195) | protected function createPushedGaeJob($job)
method createPayload (line 208) | protected function createPayload($job, $data = '', $queue = null)
method parseJobBody (line 221) | protected function parseJobBody($body)
method getQueue (line 232) | public function getQueue($queue)
method getRequest (line 242) | public function getRequest()
method setRequest (line 252) | public function setRequest(Request $request)
FILE: src/Shpasser/GaeSupportL5/Queue/Listener.php
class Listener (line 13) | class Listener extends IlluminateQueueListener
method buildWorkerCommand (line 20) | protected function buildWorkerCommand()
FILE: src/Shpasser/GaeSupportL5/Queue/QueueServiceProvider.php
class QueueServiceProvider (line 7) | class QueueServiceProvider extends LaravelQueueServiceProvider
method registerConnectors (line 15) | public function registerConnectors($manager)
method registerGaeConnector (line 27) | protected function registerGaeConnector($manager)
FILE: src/Shpasser/GaeSupportL5/Setup/Configurator.php
class Configurator (line 15) | class Configurator
method __construct (line 25) | public function __construct(Command $myCommand)
method configure (line 41) | public function configure($appId, $generateConfig, $cacheConfig, $buck...
method createEnvProductionFile (line 96) | protected function createEnvProductionFile($env_file, $env_production_...
method createEnvLocalFile (line 151) | protected function createEnvLocalFile($env_file, $env_local_file, $dbH...
method addOptimizerOptions (line 199) | protected function addOptimizerOptions(EnvHelper $env)
method processFile (line 224) | protected function processFile($filePath, $processors)
method replaceAppClass (line 251) | protected function replaceAppClass($contents)
method replaceLaravelServiceProviders (line 273) | protected function replaceLaravelServiceProviders($contents)
method setLogHandler (line 305) | protected function setLogHandler($contents)
method replaceCompiledPath (line 327) | protected function replaceCompiledPath($contents)
method addQueueConfig (line 353) | protected function addQueueConfig($contents)
method addCloudSqlConfig (line 388) | protected function addCloudSqlConfig($contents)
method addGaeDisk (line 435) | protected function addGaeDisk($contents)
method preprocessWindowsPaths (line 474) | protected function preprocessWindowsPaths($contents)
method fixCachedConfig (line 500) | protected function fixCachedConfig($contents)
method generateAppYaml (line 544) | protected function generateAppYaml($appId, $filePath, $publicPath)
method generatePhpIni (line 622) | protected function generatePhpIni($appId, $bucketId, $filePath)
method isRunningOnWindows (line 655) | protected function isRunningOnWindows()
method backupFile (line 670) | protected function backupFile($filePath)
method restoreFile (line 693) | protected function restoreFile($filePath, $backupPath, $clean = true)
FILE: src/Shpasser/GaeSupportL5/Setup/EnvHelper.php
class EnvHelper (line 5) | class EnvHelper implements \ArrayAccess
method read (line 25) | public function read($file)
method write (line 37) | public function write($file = "")
method generateEnvString (line 49) | protected function generateEnvString($array)
method parseEnvLine (line 68) | protected function parseEnvLine($line, &$key, &$value)
method findLine (line 88) | protected function findLine($key)
method offsetSet (line 111) | public function offsetSet($offset, $value)
method offsetExists (line 134) | public function offsetExists($offset)
method offsetUnset (line 154) | public function offsetUnset($offset)
method offsetGet (line 171) | public function offsetGet($offset)
FILE: src/Shpasser/GaeSupportL5/Setup/IniHelper.php
class IniHelper (line 5) | class IniHelper implements \ArrayAccess
method read (line 25) | public function read($file)
method write (line 37) | public function write($file = "")
method generateIniString (line 49) | protected function generateIniString($array)
method offsetSet (line 73) | public function offsetSet($offset, $value)
method offsetExists (line 90) | public function offsetExists($offset)
method offsetUnset (line 102) | public function offsetUnset($offset)
method offsetGet (line 115) | public function offsetGet($offset)
FILE: src/Shpasser/GaeSupportL5/Setup/SetupCommand.php
class SetupCommand (line 9) | class SetupCommand extends Command
method __construct (line 28) | public function __construct()
method handle (line 38) | public function handle()
method fire (line 49) | public function fire()
method getArguments (line 77) | protected function getArguments()
method getOptions (line 89) | protected function getOptions()
FILE: src/Shpasser/GaeSupportL5/Storage/CacheFs.php
class CacheFs (line 12) | final class CacheFs
method initialize (line 52) | public static function initialize()
method cache (line 79) | private static function cache()
method dir_list (line 100) | private function dir_list($path)
method __construct (line 120) | public function __construct()
method __destruct (line 130) | public function __destruct()
method rename (line 139) | public function rename($from, $to)
method stream_close (line 157) | public function stream_close()
method stream_eof (line 171) | public function stream_eof()
method stream_flush (line 182) | public function stream_flush()
method stream_metadata (line 216) | public function stream_metadata($path, $option, $value)
method stream_open (line 231) | public function stream_open($path, $mode, $options, &$opened_path)
method stream_read (line 285) | public function stream_read($count)
method stream_seek (line 297) | public function stream_seek($offset, $whence)
method stream_set_option (line 307) | public function stream_set_option($option, $arg1, $arg2)
method stream_stat (line 317) | public function stream_stat()
method stream_tell (line 327) | public function stream_tell()
method stream_write (line 335) | public function stream_write($data)
method unlink (line 343) | public function unlink($path)
method url_stat (line 361) | public function url_stat($path, $flags)
method dir_closedir (line 405) | public function dir_closedir()
method dir_opendir (line 416) | public function dir_opendir($path, $options)
method dir_readdir (line 427) | public function dir_readdir()
method dir_rewinddir (line 437) | public function dir_rewinddir()
method mkdir (line 452) | public function mkdir($path, $mode, $options)
method rmdir (line 492) | public function rmdir($path, $options)
FILE: src/Shpasser/GaeSupportL5/Storage/Optimizer.php
class Optimizer (line 8) | class Optimizer
method __construct (line 50) | public function __construct($basePath, $runningInConsole)
method bootstrap (line 68) | public function bootstrap()
method getCachedConfigPath (line 84) | public function getCachedConfigPath()
method getCachedRoutesPath (line 100) | public function getCachedRoutesPath()
method getCachedServicesPath (line 116) | public function getCachedServicesPath()
method initializeFs (line 124) | protected function initializeFs()
method buildFsTree (line 132) | protected function buildFsTree()
method cacheFile (line 144) | protected function cacheFile($path, $cachefsPath)
FILE: tests/Shpasser/GaeSupportL5/Setup/ConfiguratorTest.php
class ConfiguratorTest (line 12) | class ConfiguratorTest extends \PHPUnit_Framework_TestCase
method delTree (line 22) | protected static function delTree($dir)
method setUp (line 36) | protected function setUp()
method tearDown (line 63) | protected function tearDown()
method testEnvProductionGeneration (line 68) | public function testEnvProductionGeneration()
method testEnvLocalGeneration (line 75) | public function testEnvLocalGeneration()
method testBootstrapAppModification (line 82) | public function testBootstrapAppModification()
method testConfigAppModification (line 89) | public function testConfigAppModification()
method testConfigViewModification (line 96) | public function testConfigViewModification()
method testConfigQueueModification (line 103) | public function testConfigQueueModification()
method testConfigDatabaseModification (line 110) | public function testConfigDatabaseModification()
method testGenerateAppYaml (line 117) | public function testGenerateAppYaml()
method testGeneratePhpIni (line 124) | public function testGeneratePhpIni()
FILE: tests/Shpasser/GaeSupportL5/Setup/FakeCommand.php
class FakeCommand (line 7) | class FakeCommand extends Command
method info (line 12) | public function info($text)
method confirm (line 17) | public function confirm($question, $default = true)
method error (line 22) | public function error($text)
FILE: tests/Shpasser/GaeSupportL5/Setup/FakeHelpers.php
function app_path (line 6) | function app_path()
function storage_path (line 14) | function storage_path()
function base_path (line 22) | function base_path()
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (101K chars).
[
{
"path": ".gitignore",
"chars": 46,
"preview": "/vendor\ncomposer.phar\ncomposer.lock\n.DS_Store\n"
},
{
"path": ".travis.yml",
"chars": 197,
"preview": "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_re"
},
{
"path": "LICENSE",
"chars": 1099,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Ron Shpasser <shpasser@gmail.com>\n\nPermission is hereby granted, free of charg"
},
{
"path": "README.md",
"chars": 10367,
"preview": "# GaeSupport\n\n[](ht"
},
{
"path": "composer.json",
"chars": 609,
"preview": "{\n \"name\": \"shpasser/gae-support-l5\",\n \"description\": \"Google App Engine Support for Laravel 5.1 apps.\",\n \"lice"
},
{
"path": "phpunit.xml",
"chars": 570,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n backupStaticAttributes=\"false\"\n b"
},
{
"path": "src/Shpasser/GaeSupportL5/Filesystem/GaeAdapter.php",
"chars": 2419,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Filesystem;\n\nuse League\\Flysystem\\Adapter\\Local;\nuse League\\Flysystem\\Config;\n\n/*"
},
{
"path": "src/Shpasser/GaeSupportL5/Foundation/Application.php",
"chars": 5095,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Foundation;\n\nuse Illuminate\\Foundation\\Application as IlluminateApplication;\nuse "
},
{
"path": "src/Shpasser/GaeSupportL5/Foundation/gae_realpath.php",
"chars": 264,
"preview": "<?php\n\n/**\n * GAE replacement for the original realpath() function.\n */\nfunction gae_realpath($path)\n{\n $result = rea"
},
{
"path": "src/Shpasser/GaeSupportL5/GaeArtisanConsoleServiceProvider.php",
"chars": 899,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5;\n\nuse Illuminate\\Support\\ServiceProvider;\n\nclass GaeArtisanConsoleServiceProvider"
},
{
"path": "src/Shpasser/GaeSupportL5/GaeSupportServiceProvider.php",
"chars": 1196,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Illuminate\\Support\\Facades\\Storage;"
},
{
"path": "src/Shpasser/GaeSupportL5/Http/Controllers/ArtisanConsoleController.php",
"chars": 1279,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Http\\Controllers;\n\nuse Illuminate\\Routing\\Controller;\nuse Symfony\\Component\\Conso"
},
{
"path": "src/Shpasser/GaeSupportL5/Http/routes.php",
"chars": 323,
"preview": "<?php\n\nuse \\Shpasser\\GaeSupportL5\\Http\\Controllers\\ArtisanConsoleController;\n\n/**\n * Maintenance routes.\n */\nRoute::get("
},
{
"path": "src/Shpasser/GaeSupportL5/Mail/GaeTransportManager.php",
"chars": 293,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Mail;\n\nuse Illuminate\\Mail\\TransportManager;\nuse Shpasser\\GaeSupportL5\\Mail\\Trans"
},
{
"path": "src/Shpasser/GaeSupportL5/Mail/MailServiceProvider.php",
"chars": 660,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Mail;\n\nuse Shpasser\\GaeSupportL5\\Mail\\Transport\\GaeTransport;\nuse Illuminate\\Mail"
},
{
"path": "src/Shpasser/GaeSupportL5/Mail/Transport/GaeTransport.php",
"chars": 3195,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Mail\\Transport;\n\nuse Swift_Transport;\nuse Swift_Mime_Message;\nuse Swift_Events_Ev"
},
{
"path": "src/Shpasser/GaeSupportL5/Queue/GaeConnector.php",
"chars": 1122,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Queue\\Connectors\\ConnectorInt"
},
{
"path": "src/Shpasser/GaeSupportL5/Queue/GaeJob.php",
"chars": 3516,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Container\\Container;\nuse Illuminate\\Queue\\Jobs\\Job;\nuse Il"
},
{
"path": "src/Shpasser/GaeSupportL5/Queue/GaeQueue.php",
"chars": 6238,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate"
},
{
"path": "src/Shpasser/GaeSupportL5/Queue/Listener.php",
"chars": 1063,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Queue\\Listener as IlluminateQueueListener;\nuse Symfony\\Com"
},
{
"path": "src/Shpasser/GaeSupportL5/Queue/QueueServiceProvider.php",
"chars": 879,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Queue;\n\nuse Illuminate\\Queue\\QueueServiceProvider as LaravelQueueServiceProvider;"
},
{
"path": "src/Shpasser/GaeSupportL5/Setup/Configurator.php",
"chars": 21100,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse Illuminate\\Console\\Command;\nuse Artisan;\nuse Dotenv\\Dotenv;\n\n/**\n * C"
},
{
"path": "src/Shpasser/GaeSupportL5/Setup/EnvHelper.php",
"chars": 4383,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nclass EnvHelper implements \\ArrayAccess\n{\n /**\n * ENV configuratio"
},
{
"path": "src/Shpasser/GaeSupportL5/Setup/IniHelper.php",
"chars": 2790,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nclass IniHelper implements \\ArrayAccess\n{\n /**\n * INI configuratio"
},
{
"path": "src/Shpasser/GaeSupportL5/Setup/SetupCommand.php",
"chars": 2937,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse Illuminate\\Console\\Command;\nuse Symfony\\Component\\Console\\Input\\Input"
},
{
"path": "src/Shpasser/GaeSupportL5/Storage/CacheFs.php",
"chars": 11923,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Storage;\n\nuse Memcached;\n\n/**\n * A Stream Wrapper for Cache File System.\n *\n * Se"
},
{
"path": "src/Shpasser/GaeSupportL5/Storage/Optimizer.php",
"chars": 3663,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Storage;\n\n/**\n * Initializes caching of Laravel 5.1 configuration files on GAE.\n "
},
{
"path": "src/views/artisan.blade.php",
"chars": 1546,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <title>Laravel Artisan Console for GA"
},
{
"path": "tests/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "tests/Shpasser/GaeSupportL5/Setup/ConfiguratorTest.php",
"chars": 4133,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse ZipArchive;\n\n/**\n * Class ConfiguratorTest\n *\n * @package Shpasser\\Ga"
},
{
"path": "tests/Shpasser/GaeSupportL5/Setup/FakeCommand.php",
"chars": 428,
"preview": "<?php\n\nnamespace Shpasser\\GaeSupportL5\\Setup;\n\nuse Illuminate\\Console\\Command;\n\nclass FakeCommand extends Command\n{\n "
},
{
"path": "tests/Shpasser/GaeSupportL5/Setup/FakeHelpers.php",
"chars": 306,
"preview": "<?php\n\n/**\n * Return the application path\n */\nfunction app_path()\n{\n return __DIR__.'/playground/app';\n}\n\n/**\n * Retu"
}
]
About this extraction
This page contains the full source code of the shpasser/GaeSupportL5 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (92.3 KB), approximately 23.9k tokens, and a symbol index with 176 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.