Getting started with Laravel on PHP for App Engine

Introduction

This article walks through the process of deploying a Laravel 4 app to the App Engine PHP runtime.

Laravel is a web application framework that supports common tasks used in the majority of web projects, such as authentication, routing, sessions, and caching.

The Laravel 4 Starter Kit developed by Bruno Gaspar demos a simple blog application, and is a nice example of how to use the Laravel framework itself, as well as some of its packages. It should get you started on how to define and deploy your own app. If you’re not familiar with Laravel, you will find it useful to look over its documentation as well.

We’ll look at how to deploy this ‘starter kit’ app to App Engine.

Thanks and credit to Gilles Mergoil, who authored this blog post on running Laravel on a previous release of App Engine.

Step 1: Set up your local environment

First, follow the instructions on downloading the App Engine PHP SDK and setting up your local environment.
Note that to develop and test the application locally, you’ll need to have mysql installed, as described in the instructions.

Local PHP installation

For Laravel, you need to use PHP locally to run Laravel’s artisan command-line setup scripts, as well as to run the local development server.

Windows and OS X

If you’re using Windows or OS X, you should be able to use the PHP binary that comes bundled with the SDK (starting with App Engine release 1.8.5).
You don’t need to do anything special to use the PHP binary with the Launcher.

For command line scripts, once you’ve installed the SDK, you can find the PHP binary here for Windows:

<sdk path>/php/php-5.4-Win32-VC9-x86/php-cgi.exe

For OS X, you will find it here:

<path to launcher app>/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/php-cgi

Linux

For Linux, you will need to install PHP locally, as described in the documentation.  You will need to install the mcrypt extension.

Step 2: Create an App Engine App, and set up a Google Cloud SQL instance and Google Cloud Storage Bucket

Create an App Engine web application from the App Engine Administration Console.
Sign in to App Engine using your Google account. If you do not have a Google account, you can create a Google account with an email address and password.
To create a new application, click the “Create an Application” button. Follow the instructions to register an application ID, a name unique to this application. The full URL for the application will be http://your_app_id.appspot.com/.

Next, set up a Cloud SQL instance, following the instructions in Step 6 of this article.
Note the name of your Cloud project and Cloud SQL instance— you will need them for later configuration in Step 4. (If you already have created an instance that you want to use, you don’t need to create another one).

Then, set up a Google Cloud Storage (GCS) bucket. In order to read and write GCS objects, your application must have permission to access the object. See Google Cloud Storage Prerequisites for information about the setup process, more specifically steps 2–5. Add the service account name of your App Engine app as a team member to the project that contains the bucket.
Remember the name of this bucket; you will need it for later configuration.

Step 3: Get the starter kit repository and install the Laravel app

Now that you’ve set up your environment, we’re ready to start the process of installing the Laravel Starter-Kit app.

Do steps 1 and 2 of the https://github.com/brunogaspar/laravel4-starter-kit README as detailed in that document:

  • Download the repository
  • Set up composer, and run composer install.

to install the framework’s dependencies for the starter kit app. (This requires git to be installed).

Note: You may need to pass the detect_unicode=Off option to curl when you download composer:

curl -sS https://getcomposer.org/installer | php -d detect_unicode=Off

Add php.ini and app.yaml files to your project directory

Every App Engine application has a configuration file called app.yaml. Among other things, this file describes which handler scripts should be used for which URLs.

PHP App Engine applications can have a php.ini file, which lets you customize the behavior of the PHP interpreter directives.

In the top level of your project directory (the parent directory of your app directory), create the following two files, using the contents below.

php.ini

Create a php.ini file with the contents below, replacing “your_bucket_name” with the name of the Google Cloud Storage (GCS) bucket that you set up in Step 2.

; 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 = "your_bucket_name"
allow_url_include=1

Here, we’re enabling three functions that are disabled by default for App Engine, and allowing the app to include files from GCS.
We’re also allowing URL includes, which is required for the starter kit app.

app.yaml

Create an app.yaml file with the contents below, replacing ‘your-app-id’ with the ID of the App Engine app that you created in Step 2.

application: your-app-id
version: one
runtime: php
api_version: 1

handlers:

- url: /favicon\.ico
  static_files: public/favicon.ico
  upload: public/favicon\.ico

- url: /assets/(.*\.(htm$|html$|css$|js$|png$))
  static_files: public/assets/\1
  upload: public/assets/(.*\.(htm$|html$|css$|js$|png$))
  application_readable: true

- url: /.*
  script: public/index.php

Patterns are evaluated in the order they appear in the app.yaml, from top to bottom. The last handler specification, `/.*, directs all requests not already handled, to the app’s public/index.php script. This allows requests to the app to be passed on to the Laravel app’s routing logic. See the documentation for more information about .yaml runtime configuration, and how to add additional request handlers to an app if you should want to do so.

Step 4: Set up your database config

Next, we’ll set up the databases for our Laravel app. We’ll set up both a local database, for local development and testing; and a Cloud SQL database for the deployed app. For the purposes of the example, we’ll name the database laravel_db, and set up the same user account and password, for both cases.

Create a local database and user for laravel

Make sure your local MySQL server is running. Connect to your local MySQL server as the root user and run the following, where you replace ‘your_password’ with a password of your choosing.

CREATE DATABASE IF NOT EXISTS laravel_db;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';

Create a Cloud SQL database

Next, create a Cloud SQL database. Go to the Cloud SQL page for your project here, select your instance from the list, and then select the “SQL Prompt” tab for that instance. Enter and execute the following, again replacing ‘your_password’ with a password of your choosing.
(Note: you can also use the Cloud SQL command-line utility to do this setup).

CREATE DATABASE IF NOT EXISTS laravel_db;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';

Configure your app’s database settings

Next, in app.yaml, we’ll configure the database settings for both the Cloud (production) and local development environments. In the env_variables section of the file, edit the variables as appropriate for your config:

env_variables:
# change these values as appropriate:
 DEVELOPMENT_DB_HOST: 'localhost'
 DEVELOPMENT_DB_USERNAME: 'laravel_user'
 DEVELOPMENT_DB_PASSWORD: 'your_password'
 DEVELOPMENT_DB_NAME: 'laravel_db'
 PRODUCTION_CLOUD_SQL_INSTANCE: '/cloudsql/your-project-name:your-instance-name'
 PRODUCTION_DB_USERNAME: 'laravel_user'
 PRODUCTION_DB_PASSWORD: 'your_password'
 PRODUCTION_DB_NAME: 'laravel_db'

Then, in <your_project>/app/config/database.php file, add the following to the top of the file:

if(isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'],'Google App Engine') !== false) {
  $mysql_array = array(
        'driver'    => 'mysql',
        'unix_socket' => getenv('PRODUCTION_CLOUD_SQL_INSTANCE'),
        'host'      => '',
        'database'  => getenv('PRODUCTION_DB_NAME'),
        'username'  => getenv('PRODUCTION_DB_USERNAME'),
        'password'  => getenv('PRODUCTION_DB_PASSWORD'),
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    );
} else {
  $mysql_array = array(
        'driver'    => 'mysql',
        'host'      => getenv('DEVELOPMENT_DB_HOST'),
        'database'  => getenv('DEVELOPMENT_DB_NAME'),
        'username'  => getenv('DEVELOPMENT_DB_USERNAME'),
        'password'  => getenv('DEVELOPMENT_DB_PASSWORD'),
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    );
}

Then, in the connections array, set the 'mysql' key to the $mysql_array variable.

'connections' => array(

    ...

    'mysql' => $mysql_array,
    ...

Note: Laravel allows multiple config sub-directories configured to support the different environments you run in. However, the app engine development server simulates your application running in the App Engine Python runtime environment, so in general only one configuration is necessary. The SQL config is the exception.

Patch the MysqlConnector file

Patch the <your_project>/vendor/laravel/framework/src/Illuminate/Database/Connectors/MySqlConnector.php file as in this gist: https://gist.github.com/gmergoil/5693102, to support specification of the unix socket in the Cloud SQL config. Thanks to Gilles Mergoil for this patch.

Step 5: Use the artisan command to do some initial app setup

Next, we will finish initial app setup, including creating some database records, by running Step 5 from the https://github.com/brunogaspar/laravel4-starter-kit README. We’ll use Laravel’s artisan command-line interface.

From your project directory, run the following command to create your default user and user groups in the local database, and run all the necessary migrations automatically.

php artisan app:install

(See <your_project>/app/commands/AppCommand.php for more on how this command is defined).

Add a definition for gethostname()

App Engine does not currently support the gethostname() function. At the top of <your_project>/bootstrap/start.php, add the following:

require_once 'google/appengine/api/app_identity/AppIdentityService.php'
use \google\appengine\api\app_identity\AppIdentityService;

// Define the gethostname function if it does not exist
if (!function_exists('gethostname')) {
    function gethostname() {
        return AppIdentityService::getApplicationId();
    }
}    

This defines gethostname() to return the App Engine app’s ID, the same as is specified in your app.yaml file. (The details of the implementation are not important; we just need to define the function to avoid errors).

Step 6: File system-related modifications

App Engine apps can’t write to the local file system, but a stream wrapper is provided for Google Cloud Storage, and memcache is set up out of the box.
So, we will use the Memcache service for the session and cache drivers, and Google Cloud Storage for the app’s ‘storage’ directory.
We’ll also use the system log for logging.

Use the system log for logging

In <your_project>/app/start/global.php, use the system log for logging. Remove/comment out the following:

// $logFile = 'log-'.php_sapi_name().'.txt';
// Log::useDailyFiles(storage_path().'/logs/'.$logFile);

and replace it with:

use Monolog\Logger;
$monolog = Log::getMonolog();
$monolog->pushHandler(new Monolog\Handler\SyslogHandler('intranet', 'user', Logger::DEBUG, false, LOG_PID));

Use memcached for the session and cache

Set the session and cache drivers to use the (built-in) App Engine Memcache service:

in <your_project>/app/config/session.php, set:

'driver' => 'memcached',

and in <your_project>/app/config/cache.php, set:

'driver' => 'memcached',

Use Google Cloud Storage for the ‘storage’ path

Configure your app’s storage path to use one of your Cloud project’s Google Cloud Storage (GCS) buckets, as set up in Step 2.

In <your_proejct>/bootstrap/paths.php, add the following to the top of the file, replacing <your-bucket-name> with the name of your bucket as in Step 2.

const BUCKET_NAME = 'your-bucket-name';
$storage_path = "gs://" . BUCKET_NAME . "/storage";

mkdir($storage_path);   

Then, set the storage value to $storage_path:

'storage' => $storage_path,

Next, realpath() is not supported by streams (which underly App Engine’s GCS wrapper). To generalize the path initialization, if the realpath check on a path fails, we can then check whether that directory exists.

So, in <your_project>/vendor/laravel/framework/src/Illuminate/Foundation/Application.php, change the bindInstallPaths function to the following:

public function bindInstallPaths(array $paths)
{
    if (realpath($paths['app'])) {
        $this->instance('path', realpath($paths['app']));
    }
    elseif (file_exists($paths['app'])) {
        $this->instance('path', $paths['app']);
    }
    else {
        $this->instance('path', FALSE);
    }

    foreach (array_except($paths, array('app')) as $key => $value)
    {
        if (realpath($value)) {
            $this->instance("path.{$key}", realpath($value));
        }
        elseif (file_exists($value)) {
            $this->instance("path.{$key}", $value);
        }
        else {
            $this->instance("path.{$key}", FALSE);
        }
    }
}

Set up the mailer to use the app engine API

Next, we’ll modify the app to use the App Engine Mail API.

Edit the file <your_project>/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php.

At the top of the file, add:

require_once 'google/appengine/api/mail/Message.php';
use google\appengine\api\mail\Message as GAEMessage;

to use the App Engine Mail API. Then, in the send function, after this line:

$message = $message->getSwiftMessage();

replace

// return $this->sendSwiftMessage($message);

with the following. Replace ‘your-app-id’ in the sender string, with the ID of your App. (See the Mail API documentation for more information on what sender addresses you can use.)

For simplicity of example, this code snippet just grabs the message information from the already-generated SwiftMessage object (as used by the original starter kit app), and uses that information to send its email. However, you could modify the application code so that the SwiftMailer library was not used or included.

try {
    $emails = implode(', ', array_keys((array) $message->getTo()));
    $subj = $message->getSubject();
    $body = $message->getBody();
    $mail_options = [
        "sender" => "admin@your-app-id.appspotmail.com",
        "to" => $emails,
        "subject" => $subj,
        "htmlBody" => $body
    ];
    $gae_message = new GAEMessage($mail_options);
    return $gae_message->send();
} catch (InvalidArgumentException $e) {
    syslog(LOG_WARNING, "Exception sending mail: " . $e);
    return false;
}

When your application is running locally using the development server, calls to the Mail service are just printed to the log. The PHP development server does not send the email message.

Step 7: Test your app locally

We now have our app configured and ready to test locally!

Start the development web server as described in the documentation.

If you’re on Windows, you can just use the SDK’s Launcher.
(Laravel requires the mcrypt extension, which will not be bundled with the OS X Launcher until release 1.8.7. After that, you will be able to use the OS X Launcher’s bundled PHP as well).

Alternately, from the command line, start the development server with the following command, giving it the path to the PHP executable and the laravel project directory:

<path_to_sdk>/dev_appserver.py --php_executable_path=<absolute path to php-cgi> <path_to_laravel_directory>

E.g., from within the laravel directory, you can use:

<path_to_sdk>/dev_appserver.py --php_executable_path=<absolute path to php-cgi> .

If you installed PHP on OS X as described in the documentation, its path will be: /opt/local/bin/php-cgi54.

Connect to your app— by default this will usually be at http://localhost:8080/— and try it out!

You should see something like the following:

(Well, actually you should see a ‘business’ image instead of a picture of a cat. The Laravel starter kit app is using the great lorempixel.com service.)

Try logging in using the account and password that you created during the artisan app setup, and create a new post (go to ‘Blogs’ under ‘Admin’).

Step 8: Deploy your app!

Now that we’re satisfied that our app is working correctly, we will deploy it to production.

Migrate your local database to your Cloud SQL database

The production app needs its database bootstrapped as well.

For convenience, we can migrate our local database to the Cloud SQL database that we’re going to use with our app. You can do this as follows.

First, use the mysqldump command line utility to dump the database contents to a file (use the hex-blob flag):

mysqldump --databases laravel_db -u laravel_user -p --hex-blob laravel_db > laravel_file.sql

Next, set up the Cloud SQL command line tool.
Then, connect to your Cloud SQL instance as described in those docs (you will need to authenticate the first time you connect), and import the sql file that you generated above, e.g.:

./google_sql.sh <project_name>:<instance_name> < laravel_file.sql

Deploy the app

Now that we have the production database set up, we are ready to deploy the app! From the command line, you can do this from your laravel directory as follows (this example uses OAuth2):

appcfg.py update . --oauth2

That’s it! Visit your app at <your-app-id>.appspot.com, log in using the account that you created, and try creating a post!

You will also want to visit your app’s Administration Console, and take a look at the Dashboard, logs, etc.

Summary

In this article, we walked through how to test and deploy a Laravel app on PHP App Engine.

App Engine includes a Users API, which supports authentication using Google Accounts (including your own Google Apps domains).

In a follow-on post, we’ll show how to use the Users API for Laravel app authentication.