Handling sessions with Firestore

This tutorial shows how to handle sessions on App Engine.

Many apps need session handling for authentication and user preferences. PHP comes with a memory-based implementation to perform this function. However, this implementation is unsuitable for an app that can be served from multiple instances, because the session that is recorded in one instance might differ from other instances.

Objectives

  • Write the app.
  • Run the app locally.
  • Deploy the app on App Engine.

Costs

This tutorial uses the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

When you finish this tutorial, you can avoid continued billing by deleting the resources you created. For more information, see Cleaning up.

Before you begin

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the Cloud Console, on the project selector page, select or create a Cloud project.

    Go to the project selector page

  3. Make sure that billing is enabled for your Google Cloud project. Learn how to confirm billing is enabled for your project.

  4. Enable the Firestore API.

    Enable the API

  5. Install and initialize the Cloud SDK.
  6. Configure the gcloud tool to use your new Google Cloud project:
    # Configure gcloud for your project
    gcloud config set project PROJECT_ID
    Replace PROJECT_ID with the Google Cloud project ID that you created or selected previously.

Setting up the project

  1. In your terminal window, start in a directory of your choosing and create a new directory named sessions. All of the code for this tutorial is inside the sessions directory.

  2. Change into the sessions directory:

    cd sessions
    
  3. Install the dependencies:

    composer require google/cloud-firestore
    

At the end of this tutorial, the final file structure is similar to the following:

sessions
├── app.yaml
└── composer.json
├── index.php

Writing the app

  • In your terminal window, create a file called index.php with the following content:

    <?php
    /*
     * Copyright 2019 Google LLC
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    require_once __DIR__ . '/vendor/autoload.php';
    
    use Google\Cloud\Firestore\FirestoreClient;
    
    $projectId = getenv('GOOGLE_CLOUD_PROJECT');
    // Instantiate the Firestore Client for your project ID.
    $firestore = new FirestoreClient([
        'projectId' => $projectId,
    ]);
    
    $handler = $firestore->sessionHandler(['gcLimit' => 500]);
    
    // Configure PHP to use the the Firebase session handler.
    session_set_save_handler($handler, true);
    session_save_path('sessions');
    session_start();
    
    $colors = ['red', 'blue', 'green', 'yellow', 'pink'];
    /**
     * This is an example of a front controller for a flat file PHP site. Using a
     * Static list provides security against URL injection by default.
     */
    switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
        case '/':
            if (!isset($_SESSION['views'])) {
                $_SESSION['views'] = 0;
                $_SESSION['color'] = $colors[rand(0, 4)];
            }
            printf(
                '<body bgcolor="%s">Views: %s</body>',
                $_SESSION['color'],
                $_SESSION['views']++
            );
            break;
        default:
            http_response_code(404);
            exit('Not Found');
    }
    

This app displays greetings in different languages for every user. Returning users are always greeted in the same language.

Multiple app windows displaying a greeting in different languages.

Before your app can store preferences for a user, you need a way to store information about the current user in a session. This sample app uses Firestore to store session data.

You can use the Firestore session handler to use Firestore for PHP sessions.

The following diagram illustrates how Firestore handles sessions for the App Engine app.

Diagram of architecture: user, App Engine, Firestore.

After you’ve set session_set_save_handler, every request has a $_SESSION global variable that you can use to access the session. The session data is stored in Firestore.

Running locally

  1. Start the built-in PHP web server:

    php -S localhost:8080
    
  2. View the app in your web browser:

    Cloud Shell

    In the Cloud Shell toolbar, click Web preview Web preview and select Preview on port 8080.

    Local machine

    In your browser, go to http://localhost:8080

    You see one of five greetings: “Hello World”, “Hallo Welt”, "Hola mundo”, “Salut le Monde”, or “Ciao Mondo.” The language changes if you open the page in a different browser or in incognito mode. You can see and edit the session data in the Google Cloud Console.

    Firestore sessions in Cloud Console.

  3. To stop the HTTP server, in your terminal window, press Control+C.

Deploying and running on App Engine

You can use the App Engine standard environment to build and deploy an app that runs reliably under heavy load and with large amounts of data.

This tutorial uses the App Engine standard environment to deploy the server.

  1. In your terminal window, create an app.yaml file and copy the following:

    runtime: php72
    
  2. Deploy the app on App Engine:

    gcloud app deploy
    
  3. To view the live app, enter the following URL:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    Replace the following:

The greeting is now delivered by a web server running on an App Engine instance.

Deleting sessions

PHP performs session garbage collection (GC), which removes old and expired sessions according to your php.ini configuration. The Firestore session handler doesn't clean up sessions by default, but you can configure it to do so by passing a gcLimit option when creating the session handler:

$handler = $firestore->sessionHandler(['gcLimit' => 500]);

Sessions are removed using probability based session GC, or when you call the session_gc function explicitly.

Debugging the app

If you cannot connect to your App Engine app, check the following:

  1. Check that the gcloud deploy commands successfully completed and didn't output any errors. If there were errors (for example, message=Build failed), fix them, and try deploying the App Engine app again.
  2. In the Cloud Console, go to the Logs Viewer page.

    Go to Logs Viewer page

    1. In the Recently selected resources drop-down list, click App Engine Application, and then click All module_id. You see a list of requests from when you visited your app. If you don't see a list of requests, confirm you selected All module_id from the drop-down list. If you see error messages printed to the Cloud Console, check that your app's code matches the code in the section about writing the web app.

    2. Make sure that the Firestore API is enabled.

Cleaning up

Delete the project

  1. In the Cloud Console, go to the Manage resources page.

    Go to the Manage resources page

  2. In the project list, select the project that you want to delete and then click Delete .
  3. In the dialog, type the project ID and then click Shut down to delete the project.

Delete the App Engine instance

  1. In the Cloud Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

What's next