PHP での Cloud SQL for PostgreSQL の使用

PHP Bookshelf アプリ チュートリアルのこのパートでは、Cloud SQL for PostgreSQL で構造化データを作成、読み取り、更新、削除する方法を示します。

このページは複数ページからなるチュートリアルの一部です。ゼロから始めて、セットアップ手順を確認するには、PHP Bookshelf アプリに移動してください。

Cloud SQL インスタンスとデータベースの作成

Cloud SQL Proxy のインストール

Cloud SQL Proxy をダウンロードしてインストールします。Cloud SQL Proxy は、ローカルで実行中の Cloud SQL インスタンスに接続します。

Linux 64 ビット

  1. プロキシをダウンロードします。
    wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
    
  2. プロキシを実行できるようにします。
    chmod +x cloud_sql_proxy
    

Linux 32 ビット

  1. プロキシをダウンロードします。
    wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.386 -O cloud_sql_proxy
    
  2. プロキシを実行できるようにします。
    chmod +x cloud_sql_proxy
    

macOS 64 ビット

  1. プロキシをダウンロードします。
    curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64
    
  2. プロキシを実行できるようにします。
    chmod +x cloud_sql_proxy
    

macOS 32 ビット

  1. プロキシをダウンロードします。
    curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.386
    
  2. プロキシを実行できるようにします。
    chmod +x cloud_sql_proxy
    

Windows 64 ビット

https://dl.google.com/cloudsql/cloud_sql_proxy_x64.exe を右クリックして [名前を付けてリンク先を保存] を選択し、プロキシをダウンロードします。ファイル名を cloud_sql_proxy.exe に変更します。

Windows 32 ビット

https://dl.google.com/cloudsql/cloud_sql_proxy_x86.exe を右クリックして [名前を付けてリンク先を保存] を選択し、プロキシをダウンロードします。ファイル名を cloud_sql_proxy.exe に変更します。
お使いのオペレーティング システムがここに記載されていない場合は、ソースからプロキシをコンパイルすることもできます。

Cloud SQL インスタンスの作成

  1. Cloud SQL for PostgreSQL のインスタンスを作成します。

    インスタンスに library などの名前を付けます。インスタンスを使用できるようになるまで数分かかることがあります。インスタンスの準備が整うと、インスタンス リストに表示されます。

  2. Cloud SDK を使用して、次のコマンドを実行します。ここで、[YOUR_INSTANCE_NAME] は、Cloud SQL インスタンスの名前を表します。
    gcloud sql instances describe [YOUR_INSTANCE_NAME]

    出力の [CONNECTION_NAME] に示されている値を書き留めます。

    [CONNECTION_NAME] の値は [PROJECT_NAME]:[REGION_NAME]:[INSTANCE_NAME] という形式です。

Cloud SQL インスタンスの初期化

  1. 上記の手順の [CONNECTION_NAME] の値を使用して Cloud SQL Proxy を起動します。

    Linux / macOS

    ./cloud_sql_proxy -instances="[YOUR_INSTANCE_CONNECTION_NAME]"=tcp:5432

    Windows

    cloud_sql_proxy.exe -instances="[YOUR_INSTANCE_CONNECTION_NAME]"=tcp:5432

    [YOUR_INSTANCE_CONNECTION_NAME] を上記の手順で記録した [CONNECTION_NAME] の値に置き換えます。

    ローカルでテストを行うため、このステップでローカル コンピュータから Cloud SQL インスタンスへの接続を確立します。ローカルでのアプリのテストが終了するまで、Cloud SQL Proxy を実行したままにしてください。

  2. Cloud SQL のユーザーとデータベースを作成します。

    GCP Console

    1. Cloud SQL インスタンス libraryGCP Console を使用して新しいデータベースを作成します。 たとえば、名前 bookshelf を使用できます。
    2. Cloud SQL インスタンス libraryGCP Console を使用して新しいユーザーを作成します。

    Postgres クライアント

    1. 別のコマンドライン タブで、Postgres クライアントをインストールします。
      sudo apt-get install postgresql
    2. Postgres クライアント、または同様のプログラムを使用して、インスタンスに接続します。プロンプトが表示されたら、構成したルート パスワードを使用します。
      psql --host 127.0.0.1 --user postgres --password
    3. 次のコマンドを使用して、必要なデータベース、ユーザー、アクセス権限を Cloud SQL データベース内に作成します。その際、[POSTGRES_USER][POSTGRES_PASSWORD] は使用するユーザー名とパスワードに置き換えます。
      CREATE DATABASE bookshelf;
      CREATE USER [POSTGRES_USER] WITH PASSWORD '[POSTGRES_PASSWORD]';
      GRANT ALL PRIVILEGES ON DATABASE bookshelf TO [POSTGRES_USER];
      GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO [POSTGRES_USER];
      

設定の構成

  1. ターミナル ウィンドウで、getting-started-php/2-structured-data ディレクトリに移動して、settings.yml.dist ファイルをコピーします。

    cp config/settings.yml.dist config/settings.yml
    
  2. config/settings.yml を編集用に開きます。

  3. [YOUR_PROJECT_ID] は実際の GCP プロジェクト ID に置き換えます。

  4. bookshelf_backend の値を postgres に設定します。

  5. cloudsql_connection_namecloudsql_database_namecloudsql_usercloudsql_passwordcloudsql_port の値を Cloud SQL インスタンスに適切な値に設定します。postgres を使用しているため、ポート 5432 を使用します。例:

    cloudsql_connection_name: [YOUR_PROJECT_NAME]:[YOUR_REGION]:[YOUR_INSTANCE]
    cloudsql_database_name: bookshelf
    cloudsql_user: phpapp
    cloudsql_password: password
    cloudsql_port: 5432
    
  6. config/settings.yml を保存して閉じます。

デプロイする前に app.yaml ファイルを更新する必要もあります。

  1. app.yaml を編集用に開きます。

  2. beta_settings 行と cloud_sql_instances 行のコメントを解除します。

  3. cloud_sql_instances の値を config/settings.ymlcloudsql_connection_name に使用した値に設定します。この値の形式は [YOUR_PROJECT_NAME]:[YOUR_REGION]:[YOUR_INSTANCE] です。

  4. app.yaml を保存して閉じます。

依存関係のインストール

2-structured-data ディレクトリで、次のコマンドを入力します。

composer install

ローカルマシンでのアプリの実行

  1. ローカル ウェブサーバーを起動します。

    php -S localhost:8000 -t web
    
  2. ブラウザで、次のアドレスを入力します。

    http://localhost:8000

これで、アプリのウェブページを閲覧し、書籍の追加、編集、削除を行えるようになります。

App Engine フレキシブル環境へのアプリのデプロイ

  1. ターミナル ウィンドウで、サンプルアプリをデプロイします。

    gcloud app deploy
    
  2. ブラウザで、このアドレスを入力します。[YOUR_PROJECT_ID] を Google Cloud Platform(GCP)プロジェクト ID に置き換えます。

    https://[YOUR_PROJECT_ID].appspot.com
    

アプリを更新する場合は、最初にアプリをデプロイしたときと同じコマンドを入力して、更新バージョンをデプロイできます。新たにデプロイするとアプリの別のバージョンが作成され、そのバージョンがデフォルトのバージョンに昇格します。

アプリの古いバージョンはそのまま残ります。それらに関連付けられた仮想マシン(VM)インスタンスも同様です。アプリ バージョンと VM インスタンスのすべてが課金対象のリソースとなるので、ご注意ください。

アプリのデフォルト以外のバージョンを削除すると、コストを削減できます。

このチュートリアルの完了後は、作成したリソースを削除すれば、それ以降の課金は発生しません。詳細については、クリーンアップをご覧ください。

アプリの構造

この図は、アプリを構成するコンポーネントと、互いの関係を示しています。

Bookshelf アプリのデプロイ プロセスと構造

コードについて

以前は、settings.yml を編集して、bookshelf_backend の値を postgres に設定しました。この設定は、src/DataModel/Sql.php で定義された Sql クラスを読み込むようにアプリに指示します。Sql クラスは PDO API をラップし、Cloud SQL データベースに書籍情報を格納します。

controllers.php 内のこのコードは、GET '/books' ルートのハンドラを定義して登録します。$model 変数は、Sql クラスのインスタンスです。$model->listBooks メソッドは、書籍の配列とカーソルを含む配列を返します。次いで、Twig テンプレート エンジンlist.html.twig テンプレートに従って書籍のリストをレンダリングします。

$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'],
    ));
});

Cloud SQL データベースから取得された書籍のリストを生成する Twig テンプレートを以下に示します。テンプレートは、books という名前の配列変数を受け取ります。配列内の書籍ごとに、タイトルと著者が表示されます。また、テンプレートは、[詳細] ボタンが表示されるかどうかを決定する next_page_token 変数を受け取ります。

{% 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 %}

このコードは、GET '/books/{id}' ルート用のハンドラを定義して登録します。ここで、{id} は個々の書籍の ID です。ハンドラは、$model->read メソッドを呼び出して、指定された書籍を Cloud SQL から取得します。Twig テンプレート エンジンは、view.html.twig テンプレートに従って書籍をレンダリングします。

$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));
});

view.html.twig テンプレートは、book という名前の変数を受け取って、書籍のタイトル、出版日、著者、説明を表示します。

<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>

ユーザーが [Add book] をクリックすると、GET /books/add 用のハンドラが書籍に関するタイトルや著者などの情報を入力するフォームを表示します。ユーザーが [保存] をクリックすると、POST /books/add 用のハンドラがリクエストから新しい書籍を取得し、$model->create を呼び出して Cloud SQL に書籍を格納します。

$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");
});

次のテンプレートは、書籍情報の入力用フォームです。

{% 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 %}

このサンプルコードには、書籍情報を個別に編集または削除するハンドラも追加されています。

$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);
});
このページは役立ちましたか?評価をお願いいたします。