目次


PHP、MongoDB、IBM Bluemix を使ってメモ帳アプリケーションを作成する

フリーフォームのテキスト・メモをクラウド内に保管して、モバイル Web ブラウザーやデスクトップ Web ブラウザー上で検索する

Comments

今どきのコンピューター・ユーザーは、自分が所有するコンテンツ・ファイル (写真、音楽、文書など) にアクセスできるコンピューターが、1 つのデスクトップ PC やノート PC に限られることは望んでおらず、至るところで携帯端末からもオフィスのデスクトップ PC からも同じようにアクセスできることを望んでいます。こうしたニーズを満たす目的で、クラウド・ストレージ・サービスの数と種類が爆発的に増えました。そして、それぞれがストレージおよび同期機能を提供することで、ユーザーはいつでもどこからでも自分のデータにアクセスできるようになっています。

もし皆さんが、このようなサービスを構築することを考えているのであれば、今は取り掛かるのに絶好のタイミングです。クラウド・インフラストラクチャーは、これまでに比べて安価になり、安定性やスケーラビリティーが失われることのない開発者フレンドリーなものになりました。さらに、ネイティブ・モバイル・アプリケーションやモバイル Web アプリケーションを作成するためのツールが広く出回っていることから、モバイル・フレンドリーなアプリケーションを新しく開発し、テストして、デプロイする作業が、従来よりも大幅に簡単なものになっています。

このチュートリアルでは、コンピューターのユーザーがフリーフォームのテキスト・メモをクラウド内に保管して、モバイル Web ブラウザーやデスクトップ Web ブラウザーを使用して検索できるようにする、単純なメモ帳アプリケーションを作成するプロセスをひととおり紹介します。また、IBM Bluemix クラウド・プラットフォーム上にアプリケーションをデプロイして実行する方法も紹介します。

必要となるもの

このチュートリアルのサンプル・メモ帳アプリケーションは、ユーザーが作成して入力するテキスト・メモの数に制限はなく、また作成したメモをユーザーが編集、検索、削除することもできます。さらに、作成したメモを容易に分類したり識別したりできるよう、ユーザーはメモを色分けすることができます。

クライアント上では、Bootstrap を使用してアプリケーションのモバイル・フレンドリーなユーザー・インターフェースを作成し、サーバー上では、PHP マイクロフレームワークである Slim を使用して、アプリケーション・フローの管理、MongoDB との接続、MongoDB からのデータの取得を行います。

このチュートリアルのステップに従うには、以下のものが必要になります。

  • BootstrapPHPMongoDB の基礎知識
  • Apache (mod_rewrite が実装され、.htaccess ファイルに対応したもの) または nginx がインストールされたローカル PHP 開発環境
  • 構成済みのデータベース、ユーザー、パスワードを使用してローカルまたはリモートにデプロイされた MongoDB。MongoLab アカウントに登録することで、無料または有料でデプロイされた MongoDB を取得できるようになります。
  • Bluemix アカウント (無料のトライアル・アカウントを登録するか、アカウントを登録済みの場合は、そのアカウントで Bluemix にログインします)
  • PHP 依存関係マネージャーである Composer
  • CloudFoundry コマンド・ライン・ツール
  • テキスト・エディターまたは IDE

このメモ帳アプリケーションでは、高速かつスケーラブルなドキュメント・ストレージとして MongoDB を使用し、ビジネス・ロジック用に Slim PHP マイクロフレームワークを使用し、応答性に優れたモバイル・フレンドリーなユーザー・インターフェースには Bootstrap を使用します。

ステップ 1. ベア・アプリケーションの作成

  1. 最初のステップでは、Slim PHP マイクロフレームワークが含まれるベア・アプリケーションを作成します。PHP 依存関係マネージャーである Composer を使用すれば、Slim をダウンロードしてインストールすることができます。以下の Composer 構成ファイルを使用してください。このファイルは、<$APP_ROOT>/composer.json に保存する必要があります (<$APP_ROOT> は、皆さんのプロジェクトのディレクトリーです)。
    {
        "require": {
            "slim/slim": "2.*"
        }
    }
  2. 以下のコマンドで、Composer を使用して Slim をインストールします。
    shell> php composer.phar install
  3. 次に、アプリケーションのメイン制御スクリプトをセットアップします。このスクリプトは、Slim フレームワークをロードして、Slim アプリケーションを初期化します。また、このスクリプトにはアプリケーションのルートのそれぞれに対するコールバックが含まれており、それぞれのコールバックでは、送られてきたリクエストとルートが一致したときに実行されるコードを定義しています。アプリケーションは、メモの一覧表示、内容表示、追加、編集、削除、検索をサポートする必要があることから、以下に示すように URL ルートとして、/index/view/save/delete を定義することができます。このスクリプトを <$APP_ROOT>/index.php として保存します。
    <?php
    // use Composer autoloader
    require 'vendor/autoload.php';
    require 'config.php';
    
    // configure Slim application instance
    // initialize application
    $app = new \Slim\Slim(array(
      'debug' => true,
      'templates.path' => './views'
    ));
    
    $app->config = $config;
    
    // index page handlers
    $app->get('/', function () use ($app) {
      $app->redirect($app->urlFor('index'));
    });
    
    // handler to list available notes in database
    // if query string included
    // filter results to match query string
    $app->get('/index', function () use ($app) {
      // code here
    })->name('index');
    
    // handler to display add/edit form
    $app->get('/save(/:id)', function ($id = null) use ($app) {
      // code here
    });
    
    // handler to process form input
    // save note content to database
    $app->post('/save', function () use ($app) {
      // code here
    });
    
    // handler to delete specified note
    $app->get('/delete/:id', function ($id) use ($app) {
      // code here
    });
    
    // handler to display specified note
    $app->get('/view/:id', function ($id) use ($app) {
      // code here
    });
    
    
    // hook to add request URI path as template variable
    $app->hook('slim.before.dispatch', function() use ($app) {
      $app->view()->appendData(array(
        'baseUri' => $app->request()->getRootUri()
      ));
    });
    
    $app->run();
  4. 'slim.before.dispatch' というフックが、現在のリクエスト URL を (サブディレクトリー・パスを含めて) 取得して、$baseUri という名前のテンプレート変数として使用できるようにしていることに注目してください。これにより、アプリケーションを Web サーバー上の異なるディレクトリー・パスへ移植しても、ビューの中で URL パスを書き直す必要がなくなることから、移植性が最大になります。これが実際に利用されている様子は、ソース・コード・リポジトリー内の各種テンプレートにおいて見られます。
  5. また、このアプリケーションによってレンダリングされる各種ビューで使用可能な、ベースとなるユーザー・インターフェースを作成する必要もあります。その例を以下に示します。
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Cloud Notepad</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
          <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->    
      </head>
      <body>
      
        <div class="panel panel-default">
          <div class="panel-heading clearfix">
            <h4 class="pull-left">Notes</h4>
          </div>
        </div>  
    
        <!-- page content here -->
    
      
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
      </body>
    </html>

これですべてのピースが揃ったので、アプリケーションそのものの作成に取り掛かることができます。

ステップ 2. メモの追加

メモは基本的に、'title''body''color' の 3 つのプロパティーで構成されます。これらの値は、ユーザーによって提供されます。それぞれのメモには、2 つのプロパティーを追加で含めることになります。1 つは一意の 'id' であり、このプロパティーによって MongoDB コレクション内でメモが識別されます。もう 1 つは 'updated' であり、このプロパティーにメモが最後に変更された時刻が保管されます。

これらのプロパティーに適合するフォームを作成するのは簡単です。そのフォームは以下のようなコードになります。

    <form method="post" action="<?php echo $this->data['baseUri']; ?>/save">
      <input name="id" type="hidden" value="<?php echo $this->data['note']['_id']; ?>" />
      <div class="form-group">
        <label for="title">Title</label>
        <input type="title" class="form-control" id="title" name="title" placeholder="Title" value="<?php echo htmlspecialchars($this->data['note']['title']); ?>">
      </div>
      <div class="form-group">
        <label for="color">Color</label>
        <input type="color" class="form-control" id="color" name="color" placeholder="Color" value="<?php echo$this->data['note']['color']; ?>">
      </div>
      <div class="form-group">
        <label for="body">Content</label>
        <textarea name="body" id="body" class="form-control" rows="3"><?php echo htmlspecialchars($this->data['note']['body']); ?></textarea>
      </div>
      <div class="form-group">
        <button type="submit" class="btn btn-default">Save</button>
      </div>
    </form>

このフォームは、新しい input type として HTML5 'color' を使用していることに注目してください。これによって、自動的にカラー・パレットやカラー・スライダーが作られ、ユーザーはメモごとに色の範囲を選択できるようになります。選択された色は、16 進の値で返されます。

重複をなくすために、このフォームを再利用して既存のメモを編集するのが無難です。こうした理由で、フォームには隠しフィールドとして 'id' が含まれています。このフィールドは、新しいメモでは空のままとなります。システムはこの ID の存在の有無を情報として利用することで、データベース内に新しいメモを作成するか、それとも既存のメモを更新するかを判断することができます。

以下のコード・フラグメントは、<$APP_ROOT>/config.php ファイルから提供される情報を使用して、データベース接続を初期化するコードです。

<?php
// attach configuration to application
$app->config = $config;

// extract database name from URI
// initialize PHP Mongo client
$dbn = substr(parse_url($app->config['db_uri'], PHP_URL_PATH), 1);
$mongo = new MongoClient($app->config['db_uri'], array("connectTimeoutMS" => 30000));
$db = $mongo->selectDb($dbn);

フォームが /save エンドポイントに送信されると、フォーム・プロセッサーは始めに POST 入力を検証してサニタイジングを行ってから、その入力をデータベースに保存する必要があります。以下に示すのが、このプロセスのコールバック関数です。

<?php
// handler to process form input 
// save note content to database
$app->post('/save', function () use ($app, $db) {
  $collection = $db->notes;  
  $id = trim(strip_tags($app->request->post('id')));
  $note = new stdClass;
  $note->title = trim(strip_tags($app->request->post('title')));
  $note->body = trim(strip_tags($app->request->post('body')));
  $note->color = trim(strip_tags($app->request->post('color')));
  $note->updated = time();
  if (!empty($id)) {
    $note->_id = new MongoId($id);
  }
  $collection->save($note);
  $app->redirect($app->urlFor('index'));

上記コードは、POST 入力をサニタイジングして 'updated' プロパティーの値を現在時刻に設定します。そして POST 入力に ID が含まれるかどうかに応じて、メモ用の新しい MongoDB ドキュメントを作成するか、それとも POST 入力に含まれる ID を使用して、既存のドキュメントを改訂された内容で更新するかのいずれかを行います。

以下に示すのが、新しいメモを追加するフォームの一例です。

新しいメモを追加するフォームの一例のスクリーン・キャプチャー
新しいメモを追加するフォームの一例のスクリーン・キャプチャー

ステップ 3. メモの一覧表示と検索

メモを追加したり、更新したりできることは、全体像の一部に過ぎません。メモを一覧表示したり、検索したりできるようにする必要もあります。メモを一覧表示するのは簡単です。単に /index ルートに対するコールバックを更新して、MongoDB クライアントの find() メソッドを使用してコレクション内のすべてのドキュメントを取得し、取得したドキュメントを更新時刻が最新のものが先頭に来るようにソートされた状態でビューに渡すだけです。

<?php
// handler to list available notes in database
$app->get('/index', function () use ($app, $db) {
  $collection = $db->notes;  
  $notes = $collection->find()->sort(array('updated' => -1));
  $app->render('index.tpl.php', array('notes' => $notes));
})->name('index');

このコードによって出力されるフォームの一例を以下に示します。

サンプル・コードによって出力されるフォームの一例のスクリーン・キャプチャー
サンプル・コードによって出力されるフォームの一例のスクリーン・キャプチャー

メモの数が多い場合には、そのすべてを一覧表示するのはあまり現実的ではありません。1 つ以上のキーワードでメモの内容を検索する方法によって、探している情報を素早く見つけられるようにできると理想的です。

  1. 最初のステップでは、以下に示すように一覧表示用テンプレートに検索フィールドが追加されるように更新します。
        <div class="panel panel-default">
          <form method="get" action="<?php echo $this->data['baseUri']; ?>/index">
            <div class="input-group">
              <input type="text" name="q" class="form-control" placeholder="Search for...">
              <span class="input-group-btn">
                <button type="submit" class="btn btn-default">Go!</button>
              </span>
            </div>  
          </form>
        </div>
  2. ユーザーがフィールドに検索語を入力すると、'title' プロパティーや 'body' プロパティーに検索語とマッチする内容が含まれているドキュメントのみを取得するよう、汎用的な「すべてのドキュメントを検索する」ハンドラーに変更を加える必要があります。そのように変更したコードを以下に示します。
    <?php
    // handler to list available notes in database
    // if query string included
    // filter results to match query string
    $app->get('/index', function () use ($app, $db) {
      $collection = $db->notes;  
      $q = trim(strip_tags($app->request->get('q')));
      $where = array();
      if (!empty($q)) {
        $where = array(
          '$or' =>
            array(
              array(
                'title' => array('$regex' => new MongoRegex("/$q/i"))),
              array(
                'body' => array('$regex' => new MongoRegex("/$q/i")))
            )
        );  
      }
      $notes = $collection->find($where)->sort(array('updated' => -1));
      $app->render('index.tpl.php', array('notes' => $notes));
    })->name('index');

上記コードに示されるように、/index ルートに対するリクエストにクエリー・ストリングが含まれる場合、ハンドラーは、メモのタイトルまたは本文に (PHP MongoRegex オブジェクトとして表される) 検索語が含まれるメモのみを返す追加条件を生成します。この追加条件は、$where 変数の中で表現され、この変数が find() メソッドに追加の引数として渡されます。その結果として返されるデータは、前と同じようにビューに転送されて表示されます。

このサンプル・コードが動作している様子を以下に示します。

このサンプル・コードによる検索結果の一例のスクリーン・キャプチャー
このサンプル・コードによる検索結果の一例のスクリーン・キャプチャー

ステップ 4. メモの内容の表示と削除

前のステップの画像からわかるように、一覧に表示されるメモのそれぞれには「View (表示)」ボタンがあります。このボタンは /view ルートにハイパーリンクされており、クリック時に送信されるリクエストのパラメーターとして、このボタンに対応するメモのドキュメント ID が含まれています。/view コールバック・ハンドラーが行う必要があるのは、以下のコードに示すように、単に MongoDB クライアントの findOne() メソッドを使用して、指定したメモをデータベースから取得して表示することのみです。

<?php
// handler to display specified note
$app->get('/view/:id', function ($id) use ($app, $db) {
  $collection = $db->notes;
  $note = $collection->findOne(array('_id' => new MongoId($id)));
  $app->render('view.tpl.php', array('note' => $note));
});

このコードによって出力されるフォームの一例を以下に示します。

サンプル・コードによって出力されるフォームの一例のスクリーン・キャプチャー
サンプル・コードによって出力されるフォームの一例のスクリーン・キャプチャー

同様に、/delete ハンドラーはリクエストのパラメーターとしてドキュメント ID を受け取り、MongoDB クライアントの remove() メソッドを使用して、ID に対応するメモをデータベースから削除します。

<?php
// handler to delete specified note
$app->get('/delete/:id', function ($id) use ($app, $db) {
  $collection = $db->notes;
  $collection->remove(array('_id' => new MongoId($id)));
  $app->redirect($app->urlFor('index'));
});

ステップ 5. Bluemix へのデプロイ

  1. この時点で、アプリケーションは完成しており、Bluemix にデプロイすることが可能です。始めにアプリケーションの構成ファイルを更新し、データベースの資格情報に変更を加えることで、リモートにデプロイされた MongoDB データベースへ接続されるようにします。続いて、アプリケーションのマニフェスト・ファイルを作成し、使用するホストおよびアプリケーションの名前が一意となるよう、ランダムなストリング (例えば、自分の名前のイニシャルなど) を名前に追加するのを忘れないようにします。
    ---
    applications:
    - name: notes-[initials]
    memory: 256M
    instances: 1
    host: notes-[initials]
    buildpack: https://github.com/cloudfoundry/php-buildpack.git
    stack: cflinuxfs2
  2. Cloud Foundry PHP ビルドパックには、デフォルトでは PHP MongoDB 拡張モジュールは含まれていないため、デプロイ時にこの拡張モジュールが有効になるようにビルドパックを構成する必要があります。<$APP_ROOT>/.bp-config/options.json ファイルを作成して、以下の内容にします。
    {
        "WEB_SERVER": "httpd",
        "PHP_EXTENSIONS": ["bz2", "zlib", "curl", "mcrypt", "mongo"]
    }
  3. これで、アプリケーションを Bluemix にプッシュする準備ができました。
    shell> cf api https://api.ng.bluemix.net
    shell> cf login
    shell> cf push
  4. アプリケーションのマニフェストに指定したホストにブラウザーでアクセスすることで、アプリケーションを使い始めることができます (例えば、http://notes-<イニシャル>.mybluemix.net など)。空白のページが表示されたり、その他のエラーが表示されたりした場合には、「Debugging PHP Errors on IBM Bluemix」を参照してどこに問題があるかを調べてください。

まとめ

これまでは、クラウド・インフラストラクチャーとクラウド・ストレージを利用した、デスクトップまたはモバイルの Web アプリケーションを作成するのは、このチュートリアルで紹介したほど簡単ではありませんでした。Bluemix PaaS インフラストラクチャーを MongoDB、PHP、Slim フレームワーク、Bootstrap と組み合わせることで、クラウド・ベースの独自のアプリケーションを迅速かつ効率的に作成、デプロイ、スケーリングするための完全なツール・セットができあがります。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Cloud computing, Web development, Mobile development
ArticleID=1028805
ArticleTitle=PHP、MongoDB、IBM Bluemix を使ってメモ帳アプリケーションを作成する
publish-date=03242016