まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:mysql

データのマイグレーションなどで WordPress を使う場合、元データのユーザーデータごと移行するケースがあると思います。そんなユーザー情報を WordPress の機能で追加するのではなく、外部アプリケーションから直接データベースを操作して追加する方法を紹介します。

なお、今回紹介するケースでは WordPress 4.8.2 を使いました。また WordPress のテーブルプリフィクス値はデフォルトの 'wp_' であると仮定して以下を紹介します。異なる設定で利用している場合は適宜読み替えてください。加えて、今回紹介する PHP ファイルを MySQL サーバーとは異なるホストから実行する場合は、このファイル内で指定したユーザーが MySQL にリモートログインできるような設定があらかじめされている必要がある点をご注意ください。


まず WordPress を普通にセットアップすると、いくつかのテーブルが自動的に生成されます。その中でユーザー情報を管理するのは wp_users と wp_usermeta の2つです:


wp_users はその名の通り WordPress のユーザーテーブルとなっていて、1レコードが1ユーザーの情報を意味します。詳しくは後述しますが、単に「ユーザーを登録する」だけであればこのテーブルに1レコード追加するだけで事は足ります。

一方、wp_usermeta はユーザーの属性に関わる値を管理するテーブルです。例えばユーザーのニックネームや権限といった wp_users だけでは管理できない一部の情報や、システムが内部的に利用するセッション情報、最終アクション日時といった情報を wp_usermeta テーブルで管理します。以下では「管理画面にアクセスできる権限を持ったユーザーを追加する」例を紹介しますが、この場合は wp_usermeta も操作する必要があります。


まず wp_users テーブルの属性を desc コマンドで調べた結果がこちらです:
mysql> desc wp_users;
+---------------------+---------------------+------+-----+---------------------+----------------+
| Field               | Type                | Null | Key | Default             | Extra          |
+---------------------+---------------------+------+-----+---------------------+----------------+
| ID                  | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
| user_login          | varchar(60)         | NO   | MUL |                     |                |
| user_pass           | varchar(255)        | NO   |     |                     |                |
| user_nicename       | varchar(50)         | NO   | MUL |                     |                |
| user_email          | varchar(100)        | NO   | MUL |                     |                |
| user_url            | varchar(100)        | NO   |     |                     |                |
| user_registered     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| user_activation_key | varchar(255)        | NO   |     |                     |                |
| user_status         | int(11)             | NO   |     | 0                   |                |
| display_name        | varchar(250)        | NO   |     |                     |                |
+---------------------+---------------------+------+-----+---------------------+----------------+
10 rows in set (0.01 sec)

プライマリキーは ID(auto_increment)で、他のフィールドは全て Not Null 属性です。ただ user_registered と user_status にはデフォルト値が設定されています。ということはこれらを除いた user_login, user_pass, user_nicename, user_email, user_url, user_activation_key, display_name を指定すればレコードは作れそうです。

というわけで、とりあえずは以下のような関数(createWpUser)およびファイル(wp-user-import.php(改良前))を PHP で作ってみました:
<?php

$wp_db_name = 'wpdb';            //. WordPress DB
$wp_db_host = 'localhost';       //. WordPress ホスト名
$wp_db_username = 'adminuser';   //. WordPress DB の管理者ユーザー名
$wp_db_password = 'adminpass';   //. WordPress DB の管理者パスワード
$wp_table_prefix = 'wp_';        //. WordPress DB のテーブルプリフィクス


function createWpUser( $u_email, $u_password ){
  global $wp_db_name, $wp_db_host, $wp_db_username, $wp_db_password, $wp_table_prefix;

  $pdo = new PDO( 'mysql:dbname='.$wp_db_name.';host='.$wp_db_host.';charset=utf8', $wp_db_username, $wp_db_password );
  if( $pdo != null ){
     $pdo->query( 'SET NAMES utf8' );

     $sql1 = 'insert into ' . $wp_table_prefix . 'users(user_login,user_pass,user_nicename,user_email,user_url,user_activation_key,display_name) values( :user_login, MD5(:user_pass), :user_nicename, :user_email, "", "", :display_name )';
     $stmt1 = $pdo->prepare( $sql1 );
     $stmt1->bindParam( ':user_login', $u_email );
     $stmt1->bindParam( ':user_pass', $u_password );
     $stmt1->bindParam( ':user_nicename', $u_email );
     $stmt1->bindParam( ':user_email', $u_email );
     $stmt1->bindParam( ':display_name', $u_email );
     $r1 = $stmt1->execute();
  }
}

?>

createWpUser 関数のパラメータはメールアドレスとパスワードの2つだけです。メールアドレスは user_login, user_nicename, user_email, display_name に設定します。パスワードは MySQL の MD5 関数を使ってハッシュ化したものを指定します。また user_url および user_activation_key には '' を指定して insert を実行しています。

このファイルを使って、1ユーザーを作成するサンプルがこちらです(test1.php):
<?php

require_once './wp-user-import.php';

$r1 = createWpUser( 'user1@xxx.com', 'P@ssw0rd' );  //. ID: user1@xxx.com , パスワード: P@ssword のユーザーを新規作成する

?>

試しにこの test1.php を実行すると、WordPress に user1@xxx.com という ID のユーザーが登録されます:
$ php -f test1.php


管理者権限でログインして管理ページを開くと、たしかに指定したユーザーが追加されていることが確認できます(この画面で「権限グループ」が「なし」になっていることがわかります):
2017103001


この時点で作成したユーザーは有効なので、指定した ID とパスワードでログインすることはできます:
2017103002


ただ管理機能については権限を一切持っていないユーザーなので、無理やり URL を指定して管理ページにアクセスしてもエラー画面となります:
2017103003


とりあえずはここまで。wp_users に1レコード追加するだけで(管理や投稿の権限を持たない)ユーザーを作ることができることがわかりました。とりあえずこの状態になれば、後から管理者がこれらのユーザーの権限を(手動で)変える、ということも可能ではあります。


次に管理者や投稿者の権限を持ったユーザーを同様に外部から作成する方法を紹介します。先程と同様に wp_usermeta テーブルの属性を同様に調べた結果がこちらです:
mysql> desc wp_usermeta;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| umeta_id   | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| user_id    | bigint(20) unsigned | NO   | MUL | 0       |                |
| meta_key   | varchar(255)        | YES  | MUL | NULL    |                |
| meta_value | longtext            | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

umeta_id がプライマリキー(auto_increment)で、user_id は wp_users.ID を示す外部キーです。そしてこの user_id で指定したユーザーに対して、meta_key と meta_value で属性を加えることができるようになっています。

ユーザーの権限に関わる設定をするには、meta_key の値が 'wp_user_level' 及び 'wp_capabilities' となる2つのレコードを追加して指定することになります。具体的には対象のユーザーの権限に応じて、以下のような値を設定します:
ユーザー権限wp_user_level の設定値wp_capabilities の設定値
購読者0a:1:{s:10:"subscriber";b:1;}
寄稿者1a:1:{s:11:"contributor";s:1:"1";}
投稿者2~4a:1:{s:6:"author";b:1;}
編集者5~7a:1:{s:6:"editor";b:1;}
管理者8~10a:1:{s:13:"administrator";b:1;}


というわけで wp-user-import.php を少し改良します。createWpUser 関数のパラメータを2つから3つに変更し、3つ目のパラメータで上記の wp_user_level を指定するものとします:
<?php

$wp_db_name = 'wpdb';            //. WordPress DB
$wp_db_host = 'localhost';       //. WordPress ホスト名
$wp_db_username = 'adminuser';   //. WordPress DB の管理者ユーザー名
$wp_db_password = 'adminpass';   //. WordPress DB の管理者パスワード
$wp_table_prefix = 'wp_';        //. WordPress DB のテーブルプリフィクス


function createWpUser( $u_email, $u_password, $wp_user_level = 0 ){
  global $wp_db_name, $wp_db_host, $wp_db_username, $wp_db_password, $wp_table_prefix;

  $pdo = new PDO( 'mysql:dbname='.$wp_db_name.';host='.$wp_db_host.';charset=utf8', $wp_db_username, $wp_db_password );
  if( $pdo != null ){
    $pdo->query( 'SET NAMES utf8' );

    $sql1 = 'insert into ' . $wp_table_prefix . 'users(user_login,user_pass,user_nicename,user_email,user_url,user_activation_key,display_name) values( :user_login, MD5(:user_pass), :user_nicename, :user_email, "", "", :display_name )';
    $stmt1 = $pdo->prepare( $sql1 );
    $stmt1->bindParam( ':user_login', $u_email );
    $stmt1->bindParam( ':user_pass', $u_password );
    $stmt1->bindParam( ':user_nicename', $u_email );
    $stmt1->bindParam( ':user_email', $u_email );
    $stmt1->bindParam( ':display_name', $u_email );
    $r1 = $stmt1->execute();

    $user_id = null;
    $sql2 = 'select last_insert_id() as user_id from ' . $wp_table_prefix . 'users';
    $stmt2 = $pdo->query( $sql2 );
    if( $row = $stmt2->fetch( PDO::FETCH_ASSOC ) ){
      $user_id = $row['user_id'];
    }

    if( $user_id ){
      //. wp_user_level
      $sql3 = 'insert into ' . $wp_table_prefix . 'usermeta(user_id,meta_key,meta_value) values( :user_id, "wp_user_level", :wp_user_level )';
      $stmt3 = $pdo->prepare( $sql3 );
      $stmt3->bindParam( ':user_id', $user_id );
      $stmt3->bindParam( ':wp_user_level', $wp_user_level );
      $r3 = $stmt3->execute();

      //. wp_capabilities
      $wp_capabilities = null;
      if( $wp_user_level == 0 ){
        $wp_capabilities = 'a:1:{s:10:"subscriber";b:1;}';
      }else if( $wp_user_level == 1 ){
        $wp_capabilities = 'a:1:{s:11:"contributor";s:1:"1";}';
      }else if( 2 <= $wp_user_level && $wp_user_level <= 4 ){
        $wp_capabilities = 'a:1:{s:6:"author";b:1;}';
      }else if( 5 <= $wp_user_level && $wp_user_level <= 7 ){
        $wp_capabilities = 'a:1:{s:6:"editor";b:1;}';
      }else if( 8 <= $wp_user_level && $wp_user_level <= 10 ){
        $wp_capabilities = 'a:1:{s:13:"administrator";b:1;}';
      }
      if( $wp_capabilities != null ){
        $sql4 = 'insert into ' . $wp_table_prefix . 'usermeta(user_id,meta_key,meta_value) values( :user_id, "wp_capabilities", :wp_capabilities )';
        $stmt4 = $pdo->prepare( $sql4 );
        $stmt4->bindParam( ':user_id', $user_id );
        $stmt4->bindParam( ':wp_capabilities', $wp_capabilities );
        $r4 = $stmt4->execute();
      }
    }
  }
}
?>

createWpUser の3つ目のパラメータ(無指定時は 0)の値を上記の wp_user_level と見なして、wp_usermeta テーブルにユーザー権限に合わせた値を作成するように変更しました。また wp_usermeta テーブルにレコードを作成する際に wp_users.ID の値が必要になるので、MySQL の last_insert_id() 関数を使って作成したユーザーの ID を取得しています。

そして、例えば管理者権限を持ったユーザーを作成したい場合は以下(test2.php)のようなコードを用意します:
<?php

require_once './wp-user-import.php';

$r2 = createWpUser( 'user2@xxx.com', 'P@ssw0rd', 10 );  //. ID: user2@xxx.com , パスワード: P@ssword のユーザーを管理者権限で新規作成する

?>

試しにこの test2.php を実行すると、WordPress に user2@xxx.com という ID の管理者ユーザーが登録されます:
$ php -f test2.php

2017103004


このユーザーの ID とパスワードでログインすると、管理者ダッシュボードにアクセスして、管理者権限でのオペレーションが可能です:
2017103005



というわけで、PHP から WordPress の MySQL データベースを直接操作してユーザーを登録することができました!


(参考) WordPress Codex 日本語版:ユーザーの種類と権限

自分が苦手なので、備忘録的に残しておきます。

まずやりたいことはこれです。RDB (今回は MySQL)のこんなテーブル(todos)があったとします。email の人の ToDo を管理するようなテーブルだと考えてください:
列名列型目的
idintプライマリキー、自動インクリメント
emailvarchar(50)メールアドレス
bodyvarchar(255)本文
createdlongレコードを作成した日時のタイムスタンプ


これだけであれば、以下の SQL でテーブルを定義するようなものだと思います:
create table todos(
  id int primary key auto_increment,
  email varchar(50),
  body varchar(255),
  created long
);

ここに運用上の条件を1つ加えます。1つの email に対しては最新の body のみを有効としたいのです。要するに同じ email に異なる body があっても(なくても)いいのですが、有効なものは最新の(created が最も大きな)body のみとして扱えるようにしたい、というものです。

例えばテーブルの中身がこのようになっているものとします(タイムスタンプの値がいいかげんですが、大小関係だけ分かればいいものとします):
idemailbodycreated
1aaa@xxx.jpToDo その110
2bbb@xxx.jpToDo その220
3aaa@xxx.jpToDo その330


全部で3件のレコードがありますが、email = aaa@xxx.jp のレコードが2件あります。この場合は created が最大である id=3 のレコードのみが有効と考えます(id=1 のレコードは削除されていても構いません)。

このような条件を付けた上で、「有効なレコードだけの一覧を取り出したい」のですが、どのようにデータベースを定義して運用すればいいでしょう?

まず email 列は実質的にユニークになるとも考えられます。が、テーブル定義時に UNIQUE 属性を付けてしまうと、INSERT 時にエラーとなってしまうので「まず同じ email のデータが存在しているかどうかを確認し、存在していなければ INSERT 、していれば UPDATE するような SQL を実行する」ことになります(こうすると実は UNIQUE 属性はあってもなくても同じと言えます)。INSERT 時の処理がちと面倒ですが、読み込み時にはシンプルに "select * from todos" だけで実現可能な方法といえます。

次に email はユニークとは考えずに、とにかくレコードは全て INSERT して、有効なレコードを取り出す時に工夫する、という方法もあります。これは SQL としては以下を実行すればよいことになります:
> select * from todos where ( email, created ) in (select email,max(created) as max_created from todos group by email);

これはこれで実現可能な方法の1つです。ただ上記の例とは逆に、INSERT は無条件に行いますが、読み込み時に比較的複雑な SQL が実行されることになります。また有効でない(使われることのない)レコードがテーブル内にずっと残ってしまうことになります。


ではこれらのいいとこ取りを考えてみます。つまり、以下のようなロジックです:
(1) INSERT は無条件に行い、
(2) INSERT 直後に有効でないレコードがあったら削除し、
(3) 検索はシンプルに "select * from todos" だけで行えるようにする


(1) の直後に (2) が出来れば、(1) も (3) もシンプルな処理になるので理想的とも言えます。で、前書きが長くなりましたが、今回はこの (2) を実現するのに苦労した話です(苦笑)。

まず、このケースでの「有効なレコード」を取り出す場合の SQL クエリーは上記のようにこのようになります:
> select * from todos where ( email, created ) in (select email,max(created) as max_created from todos group by email);

(結果)
idemailbodycreated
2bbb@xxx.jpToDo その220
3aaa@xxx.jpToDo その330

ということは、「有効でないレコード」(削除対象のレコード)はこの SQL で取り出せます:
> select * from todos where ( email, created ) not in (select email,max(created) as max_created from todos group by email);

(結果)
idemailbodycreated
1aaa@xxx.jpToDo その110

ここまでできれば後は単純に削除対象のレコードを削除すればよいので、サブクエリーを使ってこんな SQL を実行するだけ・・・
> delete from todos where id in ( select id from todos where ( email, created ) not in (select email,max(created) as max_created from todos group by email) );

と思ったのですが、これを実行すると以下のようなエラーが発生します:
ERROR 1093 (HY000): You can't specify target table 'todos' for update in FROM clause

これがサブクエリーのややこしい(というか、自分がよく分かっていなかった)所です。サブクエリーを使った時に意識する構文がいくつか存在しています:
https://dev.mysql.com/doc/refman/5.6/ja/subquery-errors.html

↑これの「サブクエリー内の誤って使用されているテーブル」に書かれた項目が今回のエラーの原因でした。サブクエリーの FROM で指定されるテーブルと、更新系のテーブルに同じものを使用することができない、というルールです。

これを解決するにはサブクエリー内の FROM 部分("todos" と書かれた部分)を更にサブクエリーにしてテンポラリテーブルにします。上記例だとこんな感じ:
> delete from todos where id in ( select id from (select * from todos) where ( email, created ) not in ( select email,max(created)
 as max_created from (select * from todos) group by email ) );

ただこれだと該当部分が2箇所あり、今度は複数のテンポラリテーブルは異なるエイリアスを持たなければならない、という制約に引っかかってエラーになってしまいます:
ERROR 1248 (42000): Every derived table must have its own alias

というわけで、最終的な答はこちら。テンポラリテーブルに異なるエイリアスを指定して完成です:
> delete from todos where id in ( select id from (select * from todos) as temp1 where ( email, created ) not in ( select email,max(created)
 as max_created from (select * from todos) as temp2 group by email ) );

こんな SQL 、何も見ずに一発でさらっと書ける人いるんかな・・・


超有名なコンテンツ管理システムであるワードプレスは数多くのプラグインも提供されており、プラグインを組み合わせるだけで色々な目的のサイトが構築できてしまいます。

ふとした事情でクラウドファンディング的なサイトを試験的に用意する必要が生じたのですが、「ワードプレスだけでサクッとクラウドファンディングサイトが作れたりしないだろうか?」と思い立って調べてみました。結論としては「色々あるけど、とりあえずクラウドファンディングが作れるプラグインは見つけた」ので、調べて構築した内容を以下に紹介します。


まず、クラウドファンディング用のワードプレスプラグインは有料/無料あわせて数多く存在しています。その中で以下に紹介するのは IgnitionDeck という団体(?)から提供されている IDCF(IgnitionDeck CrowdFunding) (及び IDCF 対応のテーマ)です:
2017040812



IDCF をワードプレスに導入するにはワードプレス管理画面のプラグイン導入から "IgnitionDeck" を検索し、"IgnitionDeck Crowdfunding & Commerce" を見つけて「今すぐインストール」します:
2017040801


他のプラグイン同様に、インストール後「有効化」をクリックすると、このプラグインが有効になります:
2017040802


IDCF プラグインを有効にしても、この段階では IgnitionDeck 未登録のためまだ使えません。管理画面に "IgnitionDeck" というメニューが追加されているので、メニューから IDCF を選び、 "Activate Now" ボタンをクリックして登録画面に遷移します:
2017040803


登録といっても名前、メールアドレス、そしてパスワードを入力するだけです。簡単:
2017040804


登録が完了すると次に行うべき手順が紹介された画面が表示されます。↓下図の3ステップです:
2017040805


1ステップ目、(ここまでの作業でいつの間にか導入されていた)"500 Framework" テーマを有効にします:
2017040806


2ステップ目、IDCF の設定を行います(そのまま Save でも可):
2017040807


そして3ステップ目、というかクラウドファンディングサイトとしてのスタート地点です。クラウドファンディングのプロジェクトが開始できるようになったので、何か1つ追加してみます:
2017040808


プロジェクトを保存すると、ワードプレスのトップ画面のプロジェクト一覧から確認できるようになります:
2017040802


プロジェクトを選択して開くと、クラウドファンディングの説明や目標額が確認でき、支援表明もできるようになっていることが確認できます:
2017040803


IDCF には有料のオプションもあり、サポートや追加で使えるツールキットが用意されているようです。が、まだあまり調べていません。こちらは気が向いたらということで。。


軽く使ってみた限りでの感想ですが、管理画面が英語なのはともかくとして、目標額などを入力すると自動的に米ドル扱いになってしまうことに気付きました(要は金額のデータとして "100" と入力すると、自動的に「100 米ドル」扱いになってしまう)。これをカスタマイズするには IDC メニューの CURRENCY SETTINGS から "JPY" を選択して更新しておくことで変更できました:
2017040801


もう一点、ファンディングの支援レベル(例えばレベル1はステッカーのみ、レベル2は商品そのもの1つ、レベル3は商品を10個まとめて、・・・みたいなもの)を複数追加しようとしたのですが、うまくできませんでした。ググってみると、同じ現象で悩んでるっぽい人向けの FAQ っぽいページを見つけたのですが「会員専用ページです」みたいなメッセージから先に進めず、未だに原因やワークアラウンドが分かっていません。会員としてログインしてるはずなんだけどな・・・ もしかすると、ここが無料版の制限なのかもしれません:
2017040801


もう一点。実際にユーザーがこのプロジェクトを支援しようと "Support Now" して、"Next Step" に進もうとすると、、:
2017040901


こんな画面に推移します:
2017040902


ここで何らかの入力フォーム(project_purchase_form ?)が出て来る想定なんだろうけど、ここをどうすると表示できるようになるのか? カスタマイズするにはどうするのか? といった情報を探していたのですが、どうやらここから先は無料版では提供されていない機能のようでした:
Ignitiondeck does not redirect me to Purchase Page


というわけで、まだ IDCF のフル機能を試せたわけではないのですが、ここまでは超簡単に作れてしまったことも事実。便利なプラグインをまた1つ発見してしまった。。


IBM Bluemix が提供するデータサービスの1つに "Data Connect" があります:
2017040401


このサービスは DB2, Oracle, dashDB, MySQL, cloudant, ・・・など、異なるデータベースサーバー間でのデータマイグレーションを実現するものです。マイグレーション元とマイグレーション先を定義し、相互システム間の型変換などを考慮してデータを扱い、(必要であれば)マイグレーションの実行スケジュールを指定することでデータのマイグレーション処理をその場で行ったり自動化したりする、というサービスです。更に必要に応じて Secure Gateway サービスを併用することで、クラウド上に公開されたデータベースだけでなく、オンプレミスデータベースを対象にすることも可能です。


今回は Data Connect の紹介例として、社内システムで運用中の MySQL データベースが存在していると仮定し、このデータベースの一部の内容(people テーブルの内容)を統計処理するために、クラウド上の dashDB に週に一度追加マイグレーションしたい、という要望があるものとして、この要望を実現するための設定を紹介します:
2017040402


こういった処理は JDBC などを使ってプログラミングすることも可能ですが、作成したアプリケーションを自動的に定期実行したり、同様のアプリケーションを作る際の流用性が少なかったりします。 そのような要望を一元的に管理・実現するためのサービスが Data Connect です:
2017040403


例えば上記のようなケースであれば、
(1) (マイグレーション元とマイグレーション先の2つの)データベースへの接続を定義し、
(2) データセット(マイグレーション元)を定義し、
(3) セータセットとマイグレーション先の2つの結びつきを定義し、
(4) マイグレーションの実行タイミングを定義して実行
することになります。 以下ではそれぞれの手順を実際の画面を使って紹介します。


まず IBM Bluemix で Data Connect サービスインスタンスを作成し、そのサービスインスタンスの画面を開くと、このように表示されます。画面内の "LAUNCH" ボタンをクリックすることで Data Connect サービスのダッシュボード画面に移動します:
2017040408


Data Connect のダッシュボード画面です。左上の "Data Connect" という箇所をクリックすると、いつでもこの画面に戻ってこれます:
2017040401


まずは上記 (1) の(マイグレーション元とマイグレーション先の)2つのデータベースへの接続方法を定義します。左メニューで "Connections" を選択します。現在は何も定義されていないので、画面には何も表示されないはずです。では画面右上の "Create Now" をクリックします:
2017040402


接続データベースの種類を選択する画面です。今回のケースではまずマイグレーション元となる MySQL の定義をするため、"MySQL" を選択します:
2017040403


次に MySQL の詳細情報を入力します。接続名、説明(オプション)、ホスト名、ポート番号、データベース名、Secure Gateway の利用有無、そしてユーザー名/パスワードを入力し、最後に右上の "Create Connection" をクリックします:
2017040404


(おまけ)
なお、もし対象とするデータベースサーバーがプライベートネットワーク環境内にある等、インターネットから直接参照できないような場合は、Bluemix の Secure Gateway サービスを併用します。Secure Gateway サービスがプライベートネットワーク環境とのセキュアなトンネリングを実現し、このトンネルを経由してプライベートネットワーク環境のデータベースを対象とすることができるようになります。なお Bluemix の Secure Gateway の利用手順については別途紹介したエントリがあるのでこちらを参照ください:
2017040503


Secure Gateway サービスを利用する場合の Connection 定義は以下のようにして行います。"Use a secure gateway" にチェックを入れ、設定済みの Secure Gateway 接続名を選択します。その上でデータベースサーバーのホスト名(IPアドレス)やポート番号は、プライベートネットワーク内でのものを指定します:
2017040504


なお、この Secure Gateway を使ってプライベートネットワーク内のデータベースに接続する場合ですが、データベース側はゲートウェイクライアント(または同一ネットワーク)から指定したユーザー名とパスワードで外部接続できるような設定をあらかじめ行っておく必要がある点に注意してください。例えばあるユーザーに同一ネットワーク(192.168.0.*)からの参照アクセスを許可する場合であれば以下のような MySQL コマンドを実行することになります:
> grant select on (データベース名).* to (ユーザー名)@"192.168.0.%" identified by '(パスワード)';

このコマンドの後でないと(外部からの接続ができないため)上記の接続定義が作成できないはずです。設定変更後に再度接続定義を行ってください。
(おまけ終わり)


接続定義の作成に成功すると1つ前の画面に戻り、いま作成した MySQL データベースへの接続情報アイコンが追加されていることが確認できるはずです。では続けてもう1つの(マイグレーション先の)接続情報を定義するため再度 "Create New" をクリックします:
2017040405


再び接続先データベースの種類を選択する画面です。今回は IBM dashDB に接続するので、"IBM dashDB" を選択します:
2017040406


データベースの種類によって入力する詳細項目が異なります。接続先が IBM dashDB の場合は接続名、説明(オプション)、ホスト名、データベース名、Secure Gateway の利用有無、そしてユーザー名/パスワードを入力し、最後に "Create Connection" をクリックします:
2017040407


先程の画面に戻り、今度は dashDB の接続情報アイコンが追加されたことが確認できるはずです。今回はこの2つの接続情報だけで充分ですが、マイグレーションで対象とする接続先の数だけこの作業を繰り返します(または後から追加します):
2017040408


次に (2) のマイグレーション元となるデータセットを定義します。今回は MySQL データベース内の people というテーブルの情報を dashDB にマイグレーションすることが目的なので、データセットは MySQL 内の people テーブルということになります。

このテーブルをデータセットとして定義するため、Data Connect ダッシュボードの左メニューから "Data Sets" を選択します。ここでも定義済みのデータセットがまだ存在していないので何も表示されません。では画面右上の "New Data Set" をクリックします:
2017040401


定義済みの接続情報名が(下画面では2つ)表示されます。今回は MySQL データベース内のデータを取り出したいので、MySQL の接続情報名を選択します:
2017040402


次にこのデータベース接続で対象とするデータベース名を指定します。今回は1つしか定義していないので、ただ1つ表示されるデータベースを選択しますが、複数ある場合は対象のものを選択します:
2017040403


次に対象となるテーブルやビューを選択します。今回は people テーブルを対象としたいので、"people" を選択します:
2017040404


必要であれば、このテーブルから取り出す列をカスタマイズします。全ての列を取り出す場合はデフォルトのまま全てにチェックを入れてください。最後に画面右上の "Publish Data Set" をクリック:
2017040405


すると1つ前の画面に戻り、データセット一覧に今作成した people テーブルが追加されたことが確認できます。これで (2) の作業も完了しました:
2017040406


続いて (3) データの結びつきと (4) マイグレーションスケジュールを定義します。今回の例では MySQL データベースの people データセットを dashDB データベースにマイグレーションしたいので、この関係を定義します。

改めてダッシュボード画面の左メニューから "Activities" を選択します。ここに定義済みのアクティビティ一覧が表示されますが、この時点ではまだ何も表示されていません。画面右上の "Create New" で新たに1つ追加します:
2017040401


アクティビティではまずマイグレーション元を指定します。今回は最初に作成した people データセットをマイグレーション元とするので、"Data Sets" を選択します:
2017040402


Data Sets 一覧から people を選択(1つしかないけど)します。全ての列にチェックが入っていることを確認して、次にマイグレーション先を指定するため画面右上の "Copy to Target" をクリックします:
2017040403


マイグレーション先は dashDB のデータベースなので、"Connections" の dashDB の接続情報名を選択し、マイグレーション先となるデータベーススキーマを指定します:
2017040404


people テーブルをマイグレーションするのですが、このデータベースが指定したスキーマに存在していない場合はどのようにマイグレーションするかを選択します。下図では "Recreate the table"(テーブルごと作り直してマイグレーションする)を選択しています。これで定義自体は完了で、必要に応じてこのアクティビティ定義に名前を指定することもできます(画面左上から編集できます)。 そしてこの定義を保存(Save)するか、そのまま実行(Run)することもできますが、今回は更に実行スケジュールを指定します。画面右上の "Schedule Activity" をクリックします:
2017040405


カレンダーが表示されます。このマイグレーションアクティビティを実行する日付を指定します:
2017040406


次に実行時刻(指定日の何時に実行するのか)を指定します。1回だけ実行するのであればこのまま "APPLY" をクリックしてもいいのですが、定期的に繰り返して実行する場合は "Schedule this activity to repeat" を選択します:
2017040407


引き続いて繰り返しの条件を指定します。下図の例では毎日(Daily & Every 1 day)実行して、終了日を指定せず(Never)に定義しています。繰り返し条件が確定したら "APPLY" をクリックします:
2017040408


指定した実行スケジュール(の直近のもの)が画面右側に表示されます。内容を確認して画面右上の "Done" をクリックします:
2017040401


1つ前の画面に戻り、いま定義したアクティビティが追加されたことが確認できます。このアクティビティの内容を確認したり、内容を変更する場合はこのアクティビティを選択します:
2017040402


この画面からアクティビティそのものを編集したり、スケジュールを変更したり、或いはスケジュールに関係なく一度実行したりすることができます:
2017040403


このアクティビティが実行されると、元の(MySQL の)データが dashDB へマイグレーションされます。ちなみにこちらが両者のテーブル内をプレビューした様子です(前者が MySQL 、後者が dashDB):
2017040400
  ↑MySQL


2017040401
  ↑dashDB


マイグレーションの定義とスケジューリングができ、正しく動いていることが確認できました。今回のケースであれば、いわゆるトランザクション用途で使っていた MySQL のデータを、統計処理目的で dashDB に移したかったわけですが、それが簡単に実現できたことになります。

このような異なるデータベース間のデータマイグレーションは、まあパターンが1つ程度であればプログラミングで可能になるかもしれませんが、何種類ものデータベースを対象としたり、順序などが複雑に絡み合っていたり、その実行スケジュールを考慮したりすると、システマチックに済ませたくなります。そんなケースで手軽にマイグレーションが行える Data Connect はかなり便利だと感じました。


前回、LAMP 環境を構築した IBM LinuxONE サーバーを使って、PHP のメジャーな MVC フレームワークの1つであるCakePHP の環境を整えてみます:
IBM LinuxONE コミュニティクラウド上で LAMP 環境を作る


まず CakePHP を動かす場合の PHP 設定を行います。/etc/php.ini を適宜変更するのですが、最低限やっておかないといけないのが、タイムゾーンの設定です。/etc/php.ini を編集して、以下の設定を加えます([Date] カテゴリ内でコメントアウトされている Date.timezone の設定からコメントを外し、"Asia/Tokyo" に設定して保存します:
$ sudo vi /etc/php.ini

  :
  :
[Date]
Date.timezone = "Asia/Tokyo"
  :
  :

また CakePHP では PHPUnit というモジュールを使うことになるので、これもインストールしておきます。前回の LAMP 環境構築時に pear もインストールしているので、pear を使って PHPUnit を導入します:
$ sudo pear install pear/PHPUnit

次に MySQL の設定を行います。まずは文字コードの設定を変更して、デフォルトで UTF-8 を使えるようにします:
$ sudo vi /etc/my.cnf

[mysqld]
character-set-server=utf8

[mysql]
default-character-set=utf8



今回は CakePHP 専用のデータベース(cakedb)を新たに作成し、その中に商品情報を格納するマスターテーブル(items)と、商品カテゴリを格納するマスターテーブル(categories)を定義することにします:
$ mysql -u root -p

mysql> create database cakedb default character set utf8;
mysql> use cakedb
mysql> create table items( id int primary key auto_increment, name varchar(50), category_id int, created datetime default null, modified datetime default null );
mysql> create table categories( id int primary key auto_increment, name varchar(50), created datetime default null, modified datetime default null );
mysql> quit

1つのデータベースと、2つのテーブルを作りました。必要に応じてこれらにアクセスできるユーザーなどを追加しましょう。

これら2つのマスターテーブルを操作できるような環境を CakePHP で作ることにしましょう。ここは必須ではありませんが、データベースを操作する上では phpMyAdmin があると便利です。IBM LinuxONE コミュニティクラウドのサーバーに phpMyAdmin 環境を導入する場合の手順はこちらの記事を参考にしてください:
IBM LinuxONE コミュニティクラウド上に phpMyAdmin を導入する


ではいよいよメインディッシュの CakePHP 環境を構築します。PHP 5.3 でも動くよう、CakePHP のバージョンは 2.x を使うことにします。またこれも Apache HTTPD のドキュメントルートがデフォルトのままの /var/www/html であるとして作業を紹介します:
$ https://github.com/cakephp/cakephp/archive/2.9.4.zip
$ sudo /bin/bash
# cd /var/www/html
# unzip ~linux1/2.9.4.zip
# mv cakephp-2.9.4 cakephp
# chmod 777 -R cakephp/app/tmp
# chmod 755 cakephp/lib/Cake/Console/cake
# cd cakephp/app/Config
# vi core.php

  :
  :
/**
 * A random string used in security hashing methods.
 */
        Configure::write('Security.salt', 'ABCDabcd1234');

/**
 * A random numeric string (digits only) used to encrypt/decrypt strings.
 */
        Configure::write('Security.cipherSeed', '1234567890');
  :
  :

最後の cakephp/app/Config/core.php の編集作業では 'Security.salt' の値と、'Security.cipherSeed' の値はデフォルトの(既知の)値のままだと危ないのでランダムな値に書き換えました。

続けて(カレントディレクトリが cake/app/Config/ の状態で)、更にデータベースの設定ファイルを用意して、自分の環境に合わせた設定を行います:
# cp database.php.default database.php
# vi database.php

  :
  :
class DATABASE_CONFIG {

        public $default = array(
                'datasource' => 'Database/Mysql',
                'persistent' => false,
                'host' => 'localhost',
                'login' => 'root',
                'password' => 'P@ssw0rd',
                'database' => 'cakedb',
                'prefix' => '',
                //'encoding' => 'utf8',
        );
  :
  :

↑具体的にはユーザー名(login)、パスワード(password)、データベース(database)の値を書き換えます。


CakePHP 自体はここまでの設定で動くはずです。必要に応じて DebugKit などの便利なプラグインを cakephp/app/Plugin/ 以下に追加で導入してください。DebugKit の導入に関してはこちらを参照ください:
https://github.com/cakephp/debug_kit/tree/2.2


ブラウザで http://(IPアドレス)/cakephp/ にアクセスすると、CakePHP のホーム画面が表示されます。緑と黄色のバーが並んでいる状態であれば、少なくとも設定は間違っていないことになります。下図では DebugKit まで導入して、全て緑になっている状態です:
2017010603

 

このページのトップヘ