herokuでPHPからMySQL(ClearDB)を使う

今日は、herokuでPHPからMySQLを使ってみたいと思います。

クレジットカード情報の登録

ClearDBを使用するには、無料プランでもクレジットカードの登録が必要になります。

herokuにログインし、「Manage Account」の「Billing」からクレジットカード情報を登録します。

Gitリポジトリの用意

まず、Gitリポジトリを作成します。

$ mkdir heroku-mysql
$ cd heroku-mysql/
$ touch composer.json
$ git init
$ git add -A
$ git commit -m "Initial commit"

herokuアプリの作成

herokuのアプリを作成します。

$ heroku create
https://damp-thicket-1597.herokuapp.com/ | https://git.heroku.com/damp-thicket-1597.git
Git remote heroku added

ClearDBアドオンをインストールします。igniteは無料プラン(データベースサイズ5MB)です。

$ heroku addons:add cleardb:ignite
Adding cleardb:ignite on damp-thicket-1597... done, v3 (free)
Use `heroku addons:docs cleardb` to view documentation.

composer.jsonを編集します。

composer.json

{
  "require": {
    "php": "~5.5.18"
  }
}

Procfileを作成します。

Procfile

web: vendor/bin/heroku-php-apache2 public

Apacheのドキュメントルートをpublicに変更しています。

ClearDBの設定

データベース接続情報を取得します。

$ heroku config
=== damp-thicket-1597 Config Vars
CLEARDB_DATABASE_URL: mysql://b3805c079c8eb9:40b9ad89@us-cdbr-iron-east-01.cleardb.net/heroku_c33169f285b123f?reconnect=true

上記の情報でmysqlコマンドを使いデータベースにアクセスします。なお、SSL設定をしていませんので通信は暗号化されていませんので注意してください。

$ mysql --host=us-cdbr-iron-east-01.cleardb.net --user=b3805c079c8eb9 --password=40b9ad89 heroku_c33169f285b123f

テスト用のテーブルを作成します。

mysql> CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(64),
    data_created DATETIME
);

データを挿入します。

mysql> INSERT INTO user VALUES (1, 'user1', NOW());
mysql> INSERT INTO user VALUES (2, 'user2', NOW());
mysql> INSERT INTO user VALUES (3, 'user3', NOW());

確認します。

mysql> SELECT * FROM user;
+----+-------+---------------------+
| id | name  | data_created        |
+----+-------+---------------------+
|  1 | user1 | 2014-12-11 10:48:16 |
|  2 | user2 | 2014-12-11 10:48:26 |
|  3 | user3 | 2014-12-11 10:48:31 |
+----+-------+---------------------+
3 rows in set (0.18 sec)

index.phpの作成

public/index.php

<?php

$db = parse_url($_SERVER['CLEARDB_DATABASE_URL']);
$db['dbname'] = ltrim($db['path'], '/');
$dsn = "mysql:host={$db['host']};dbname={$db['dbname']};charset=utf8";

try {
    $db = new PDO($dsn, $db['user'], $db['pass']);
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $sql = 'SELECT * FROM user';
    $prepare = $db->prepare($sql);
    $prepare->execute();

    echo '<pre>';
    $prepare->execute();
    $result = $prepare->fetchAll(PDO::FETCH_ASSOC);
    print_r(h($result));
    echo "\n";
    echo '</pre>';
} catch (PDOException $e) {
    echo 'Error: ' . h($e->getMessage());
}

function h($var)
{
    if (is_array($var)) {
        return array_map('h', $var);
    } else {
        return htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
    }
}

herokuへのデプロイ

herokuにデプロイします。

$ git add -A
$ git commit -m "Initial commit"
$ git push heroku master
Counting objects: 9, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (9/9), 1.09 KiB | 0 bytes/s, done.
Total 9 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> PHP app detected
remote: -----> Resolved composer.json requirement for PHP ~5.5.18 to version 5.5.19.
remote: -----> Installing system packages...
remote:        - PHP 5.5.19
remote:        - Apache 2.4.10
remote:        - Nginx 1.6.0
remote: -----> Installing PHP extensions...
remote:        - zend-opcache (automatic; bundled, using 'ext-zend-opcache.ini')
remote: -----> Installing dependencies...
remote:        Composer version 1.0-dev (4a3bc58adfd501fa6e82c82fdd9e8d4036898fa1) 2014-12-10 15:36:16
remote:        Loading composer repositories with package information
remote:        Installing dependencies
remote:        Nothing to install or update
remote:        Generating optimized autoload files
remote: -----> Preparing runtime environment...
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: 
remote: -----> Compressing... done, 70.8MB
remote: -----> Launching... done, v4
remote:        https://damp-thicket-1597.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/damp-thicket-1597.git
 * [new branch]      master -> master

確認

完了したら、ブラウザからアクセスしてみましょう。

$ heroku open

以下のように表示されればOKです。

Array
(
    [0] => Array
        (
            [id] => 1
            [name] => user1
            [data_created] => 2014-12-11 10:48:16
        )

    [1] => Array
        (
            [id] => 2
            [name] => user2
            [data_created] => 2014-12-11 10:48:26
        )

    [2] => Array
        (
            [id] => 3
            [name] => user3
            [data_created] => 2014-12-11 10:48:31
        )

)

参考

関連

Tags: heroku, mysql, php, database

普通じゃないモッキングフレームワークAspectMockがパワフル過ぎる

この記事はPHP Advent Calendar 2014の10日目です。昨日はwateradaさんの「PHPコードのレビュー結果を共有してみる」でした。

今日は、イケてるモッキングフレームワークAspectMockを紹介します。

テストに関するよくある誤解

以下のようなクラスがある場合、Model_FormがActive Recordだとして、「このクラスはデータベースを操作せずにユニットテストすることができません」と言う人がいます。

class Controller_Admin_Form extends Controller_Admin
{
    public function action_index()
    {
        $data['forms'] = Model_Form::find(
            'all', 
            array('order_by' => array('created_at' => 'desc'))
        );
        $this->template->title = "問い合わせ";
        $this->template->content = View::forge('admin/form/index', $data);
    }
}

Model_Form::find()メソッドがハードコードされているためです。

しかし、Model_Form::find()をテストダブル(モック)で置き換えることができれば、データベースなしでテストすることはもちろん可能です。

それ、AspectMockなら簡単にできます。

test::double('Model_Form', ['find', []]);

これで、Model_Form::find()は空の配列を返すようになります。

データベースを操作せずにテスト可能になりました。つまり、静的メソッドがテストできないというのはAspectMockがなかった時代の古い知識です。現在は、正しくありません。

Note: 静的メソッドをハードコードするコーディングを推奨しているわけではありません。テストできないという説は単に正しくないと言っているだけです。

Note: AspectMock以前でもrunkitなどのPHPの機能拡張を使えば同じようなことは実現できました。

AspectMockとは?

AspectMockは普通じゃないPHPのモッキングフレームワーク。アスペクト指向プログラミングとイケてるGo-AOPライブラリのパワーで、AspectMockはほとんど全てのPHPコードのスタブとモックを作成できる! https://github.com/Codeception/AspectMock

AspectMockには以下のような特徴があります。

  • 静的メソッドのテストダブルの作成が可能
  • メソッドの動的な変更が可能
  • 名前空間内の関数のテストダブルの作成が可能
  • 覚えられるシンプルなシンタックス

AspectMockの要件

PHP 5.4以上が必要です。

AspectMockのインストールと設定

AspectMockは、Composerから簡単にインストールできます。

$ composer require codeception/aspect-mock:* --dev

注意

  • 重いので本番環境にはインストールしない方がよい
  • 必ずPHPUnitもプロジェクト内にインストールする

インストールが完了したら、テスト実行時にAspectMockを使うように設定します。Composerのオートローダを使っている場合は以下のように設定します。

require __DIR__.'/../vendor/autoload.php'; // Composerのオートローダ

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'debug' => true,
    'includePaths' => [__DIR__.'/../src'],
    'cacheDir' => __DIR__.'/cache/AspectMock',
]);

$kernel->init()の引数

項目 説明
appDir Webアプリのルートフォルダ。デフォルトはComposerのvendorフォルダのあるフォルダ
includePaths AOPを適用して置き替えたいフォルダ
excludePaths AOPを適用しないフォルダ。テストフォルダは指定すべき
cacheDir キャッシュフォルダ

PHPUnitの設定

続いて、PHPUnitからAspectMockを使うための設定をします。まず、backupGlobalsを必ずfalseにします。

phpunit.xml

<phpunit bootstrap="bootstrap.php" backupGlobals="false">

そして、tearDown()メソッドで登録したテストダブルを削除するようにします。

TestCase

<?php
use AspectMock\Test as test;

abstract class TestCase extends \PHPUnit_Framework_TestCase
{
    protected function tearDown()
    {
        test::clean(); // 登録したテストダブルを削除
    }
}

AspectMockの使い方

それでは、AspectMockの使い方を見ていきましょう。

静的メソッドの置き換え

静的メソッドを置き換える構文は以下のようなシンプルなものです。

構文

test::double('クラス名', ['メソッド名' => 返り値]);

 @return ClassProxy

これなら覚えられますね。この場合、ClassProxyクラスのオブジェクトが返ります。

指定クラスの指定メソッドを置き換える
$fs = test::double('Fuel\Core\Fieldset', ['repopulate' => true]);

これでFuel\Core\Fieldsetクラスのrepopulate()メソッドはtrueを返すようになります。

例外を発生させる
$model_mail = test::double(
    'Model_Mail',
    ['send' => function() { throw new EmailSendingFailedException; }]
);

これでModel_Mailクラスのsend()メソッドは例外EmailSendingFailedExceptionを投げるようになります。

引数の値によりテストダブルが返す値を変更する

引数の値によりテストダブルの動作を変更することも可能です。

test::double('Fuel\Core\Config', ['get' => function ($arg) {
    if ($arg === 'foo.bar') {
        return 'foo.bar';
    } else {
        return 'baz';
    }
}]);

引数の値がfoo.barの場合は、foo.barが返り、その他の場合はbazが返ります。

引数の値により実際のメソッドを実行する

引数の値により実際のメソッドを実行することも可能です。

test::double('Fuel\Core\Config', ['get' => function ($arg) {
    if ($arg === 'foo.bar') {
        return 'foo.bar';
    } else {
        // モックせずに実際のメソッドを実行させる
        return __AM_CONTINUE__;
    }
}]);

関数の置き換え

名前空間内にある関数は同じように置き換えできます。

構文

test::func('名前空間', '関数名', 返り値);

 @return FuncProxy
PHP内部関数の置き換え
$func = test::func(__NAMESPACE__, 'header', '');

現在の名前空間内でheader()関数が空文字列を返すだけになります。

メソッド呼び出しの検証

メソッドが呼び出されたことを検証することももちろんできます。

$user = test::double(new User, ['getName' => 'davert']);
$this->assertEquals('davert', $user->getName());

$user->verifyMethodInvoked('getName');                 // 呼び出されたか?
$user->verifyMethodInvoked('setName', ['davert']);     // 引数の指定
$user->verifyMethodInvokedOnce('getName');             // 一度だけ?
$user->verifyMethodNeverInvoked('setName');            // 呼び出されない?
$user->verifyMethodInvokedMultipleTimes('setName', 1); // 呼び出し回数の指定

これらのメソッドは、Proxyクラス(ClassProxyおよびInstanceProxy)が持つメソッドになります。

モックオブジェクトを取得するには?

タイプヒントで指定のタイプのモックが必要な場合は、Proxyクラスからモックオブジェクトを取得します。

ClassProxyの場合

// コンストラクタを呼ばずにモックオブジェクトを取得
$user = test::double('User')->make(); 

// コンストラクタを呼び出しモックオブジェクトを取得
$user = test::double('User')->construct([
     'name' => 'davert',
     'email' => 'davert@mail.ua'
]);

InstanceProxyの場合

$user = test::double(new User)->getObject();

AspectMockの仕組み

さて、どうしてAspectMockではこのようなことが可能なのでしょう?

AspectMockは、AOP(アスペクト指向プログラミング)によりモックするメソッドをインターセプトして置き換えます。

具体的には、Go AOPライブラリを使いPHPファイルを、以下のように動的に書き換えています。

class User
{
    function setName($name)
    {
        $this->name = $name;
    }    
}

class User
{    
    function setName($name)
    { if (($__am_res = __amock_before($this, __CLASS__, __FUNCTION__, 
array(), false)) !== __AM_CONTINUE__) return $__am_res; 
        $this->name = $name;
    }    

}

このようにして、指定のメソッドをテストダブルで置き換えるかどうかという処理をしているわけです。

さらに知るには?

さらに知りたい場合は、AspectMockのリポジトリや解説記事などをご覧願います。公式のドキュメント(英語)はリポジトリにあります。

(2014-12-10 13:38 追記)

AspectMockのまとめ

  • 静的メソッドや関数のテストダブルを作成できます
  • テストダブル作成の構文は簡単で覚えられます
  • テストできないものはほぼありません
  • ガンガンテストしましょう♪

この記事はPHP Advent Calendar 2014の10日目です。明日はne_sachirouさんの「PHPで簡単に華麗にDIとAOPをキメる」です。

Tags: php, aspectmock, phpunit, testing

NetBeansでPhalconのVoltをハイライト表示する

PhalconのテンプレートエンジンVoltは、Twigに似ているので、拡張子voltをTwigを指定すればいいです。

「ツール」メニューから「オプション」を選択し、「その他」の「ファイル」タブを選びます。

「ファイルの拡張子」に「新規」ボタンでvoltを追加して、「関連付けられたファイル・タイプ(MIME)」に「TWIG (text/x-twig)」を選択します。

関連

Tags: netbeans, phalcon