Using Cloud Datastore with PHP

This part of the PHP Bookshelf tutorial shows how to create, read, update, and delete structured data in Google Cloud Datastore.

This page is part of a multi-page tutorial. To start from the beginning and see instructions for setting up, go to PHP Bookshelf App.

Configuring settings

  1. Go to the getting-started-php/2-structured-data directory, and copy the settings.yml.dist file:

    cp config/settings.yml.dist config/settings.yml
    
  2. Open config/settings.yml for editing.

  3. Replace YOUR_PROJECT_ID with your project ID.

  4. Set the value of bookshelf_backend to datastore.

  5. Save and close settings.yml.

Cloud Datastore is a fully managed service that is automatically initialized and connected to your App Engine app. No further configuration is required.

Installing dependencies

In the 2-structured-data directory, enter this command.

composer install

Running the app on your local machine

  1. Start a local web server:

    php -S localhost:8000 -t web
    
  2. In your web browser, enter this address.

    http://localhost:8000

Now you can browse the app's web pages and add, edit, and delete books.

Deploying the app to the App Engine flexible environment

  1. Deploy the sample app:

    gcloud app deploy
    
  2. In your web browser, enter this address. Replace [YOUR_PROJECT_ID] with your project ID:

    https://[YOUR_PROJECT_ID].appspot.com
    

    If you update your app, you can deploy the updated version by entering the same command you used to deploy the app the first time. The new deployment creates a new version of your app and promotes it to the default version. The older versions of your app remain, as do their associated VM instances. Be aware that all of these app versions and VM instances are billable resources.

    You can reduce costs by deleting the non-default versions of your app.

    To delete an app version:

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

      Go to the Versions page

    2. Click the checkbox next to the non-default app version you want to delete.
    3. Click the Delete button at the top of the page to delete the app version.

    For complete information about cleaning up billable resources, see the Cleaning up section in the final step of this tutorial.

    Application structure

    The following diagram shows the application's components and how they fit together.

    Bookshelf app deployment process and structure

    Understanding the code

    Previously, you edited settings.yml and set the value of bookshelf_backend to datastore. This tells the app to load the Datastore class, which is defined in src/DataModel/Datastore.php. The Datastore class wraps the Cloud Datastore API, and is responsible for storing books in your Cloud Datastore database.

    This code in controllers.php defines and registers a handler for the GET '/books/' route. The $model variable is an instance of the Datastore class. The $model->listBooks method returns an array that contains an array of books and a cursor. Then the Twig template engine renders the list of books according to the list.html.twig template:

    $app->get('/books/', function (Request $request) use ($app) {
        /** @var DataModelInterface $model */
        $model = $app['bookshelf.model'];
        /** @var Twig_Environment $twig */
        $twig = $app['twig'];
        $token = $request->query->get('page_token');
        $bookList = $model->listBooks($app['bookshelf.page_size'], $token);
    
        return $twig->render('list.html.twig', array(
            'books' => $bookList['books'],
            'next_page_token' => $bookList['cursor'],
        ));
    });

    Here is the Twig template for listing books that are retrieved from Cloud Datastore. The template receives an array variable named books. For each book in the array, it displays the title and author. The template also receives a next_page_token variable that determines whether the More button is displayed:

    {% for book in books %}
    <div class="media">
      <a href="/books/{{book.id}}">
        <div class="media-left">
          <img src="http://placekitten.com/g/128/192">
        </div>
        <div class="media-body">
          <h4>{{book.title}}</h4>
          <p>{{book.author}}</p>
        </div>
      </a>
    </div>
    {% else %}
    <p>No books found</p>
    {% endfor %}

    This code defines and registers a handler for the GET '/books/{id}' route, where {id} is the ID of an individual book. The handler calls the $model->read method to get the specified book from Cloud Datastore. The Twig template engine renders the book according to the view.html.twig template:

    $app->get('/books/{id}', function ($id) use ($app) {
        /** @var DataModelInterface $model */
        $model = $app['bookshelf.model'];
        $book = $model->read($id);
        if (!$book) {
            return new Response('', Response::HTTP_NOT_FOUND);
        }
        /** @var Twig_Environment $twig */
        $twig = $app['twig'];
    
        return $twig->render('view.html.twig', array('book' => $book));
    });

    The view.html.twig template receives a variable named book, and displays the book's title, publication date, author, and description:

    <div class="media">
      <div class="media-body">
        <h4 class="book-title">
          {{book.title}}
          <small>{{book.published_date}}</small>
        </h4>
        <h5 class="book-author">By {{book.author|default('Unknown', True)}}</h5>
        <p class="book-description">{{book.description}}</p>
      </div>
    </div>

    When the user clicks Add book, the handler for GET /books/add displays a form for entering the title, author and other information about a book. When the user clicks Save, the handler for POST /books/add gets the new book from the request, and calls $model->create to store the book in Cloud Datastore:

    $app->get('/books/add', function () use ($app) {
        /** @var Twig_Environment $twig */
        $twig = $app['twig'];
    
        return $twig->render('form.html.twig', array(
            'action' => 'Add',
            'book' => array(),
        ));
    });
    
    $app->post('/books/add', function (Request $request) use ($app) {
        /** @var DataModelInterface $model */
        $model = $app['bookshelf.model'];
        $book = $request->request->all();
        $id = $model->create($book);
    
        return $app->redirect("/books/$id");
    });

    Here's the template for the book entry form:

    {% extends "base.html.twig" %}
    
    {% block content %}
    <h3>{{action}} book</h3>
    
    <form method="POST" enctype="multipart/form-data">
    
      <div class="form-group">
        <label for="title">Title</label>
        <input type="text" name="title" id="title" value="{{book.title}}" class="form-control"/>
      </div>
    
      <div class="form-group">
        <label for="author">Author</label>
        <input type="text" name="author" id="author" value="{{book.author}}" class="form-control"/>
      </div>
    
      <div class="form-group">
        <label for="published_date">Date Published</label>
        <input type="text" name="published_date" id="published_date" value="{{book.published_date}}" class="form-control"/>
      </div>
    
      <div class="form-group">
        <label for="description">Description</label>
        <textarea name="description" id="description" class="form-control">{{book.description}}</textarea>
      </div>
    
      <button id="submit" type="submit" class="btn btn-success">Save</button>
    </form>
    
    {% endblock %}

    The sample code includes additional handlers for editing and deleting individual books:

    $app->get('/books/{id}/edit', function ($id) use ($app) {
        /** @var DataModelInterface $model */
        $model = $app['bookshelf.model'];
        $book = $model->read($id);
        if (!$book) {
            return new Response('', Response::HTTP_NOT_FOUND);
        }
        /** @var Twig_Environment $twig */
        $twig = $app['twig'];
    
        return $twig->render('form.html.twig', array(
            'action' => 'Edit',
            'book' => $book,
        ));
    });
    
    $app->post('/books/{id}/edit', function (Request $request, $id) use ($app) {
        $book = $request->request->all();
        $book['id'] = $id;
        /** @var DataModelInterface $model */
        $model = $app['bookshelf.model'];
        if (!$model->read($id)) {
            return new Response('', Response::HTTP_NOT_FOUND);
        }
        if ($model->update($book)) {
            return $app->redirect("/books/$id");
        }
    
        return new Response('Could not update book');
    });
    $app->post('/books/{id}/delete', function ($id) use ($app) {
        /** @var DataModelInterface $model */
        $model = $app['bookshelf.model'];
        $book = $model->read($id);
        if ($book) {
            $model->delete($id);
    
            return $app->redirect('/books/', Response::HTTP_SEE_OTHER);
        }
    
        return new Response('', Response::HTTP_NOT_FOUND);
    });

Send feedback about...