旅するえんじにあ - Engineers to Travel -

旅するエンジニアの気まま備忘録

【MySQL】 データベース内のテーブルを全てTRUNCATE

たまにありますよね。 phpMyAdminとか使えば簡単かもしれないんですけど データベース内に数百というテーブルが存在していて、これ全部Truncateかけたいんだけど。。。

show tablesでテーブルリスト作ってそこにtruncateを入れて動かす。 そんなんでもいいんですが、1ライナーでさくっと消してやりましょう。

mysql -u[user] -p[password] [database name] -N -e  'show tables' | xargs -IARG mysql -u[user] -p[password] -e 'truncate table ARG' [database name]

上に書いてある通りです。 show tablesで出てくる各テーブルに対してtruncateつけて実行しているだけです。 これで指定したデータベース内は全てtruncateされます。

あぁ、すっきり。

【vim】検索によるシンタックスハイライトを解除する

別にVimerではないんですが 簡単な修正程度であれば面倒なんでviでやっちゃうときあります。 にわかにちょっとだけコマンド知ってるぜって思いながらやってるのですが。

たまに検索に

/php

なんてコマンドを使うと そのファイル内の検索を行ってくれるのですが そのままだとハイライトが消えないのです。 他のファイル開いてもphpがハイライトされる。

めっちゃ気になるんですが。

ってことで検索してハイライトした部分を解除する方法

:noh

これやればOKです。

せっかくなのでvimの設定にショートカットとして登録しておいたりすると便利ですよね。 例えばESCを2回叩いたらハイライトを消すとかね。 ちょっとESCは多様するので自分はやらないのですが vimrcに以下のように記述するとできます。

nnoremap<ESC><ESC>:nohlsearch<CR>

また、ハイライトについても

set hlsearch

これでハイライトをONにします。

set nohlsearch

これでハイライトをOFF

そこまで使わないとはいえ、知っておくと便利だったので、備忘録のためにも。

【PHP】 苦悩の末導入したPHPUnitとDBUnit

やっと忙しい時期がある程度落ち着いたので 久々の更新です。

もちろんコードも書いていますが やはり運用をしていくうえで必要なのがテストです。

これは初めすごく面倒なのですが、一度やってしまうと テストなしではコードが書けなくなります。 自分も最初これを言われた時には何を言ってんだ。。。 と思ったのですが、いざやってみるとまさに・・・

特に今回は有名フレームワークではなく、 MonobitエンジンにおけるMWFフレームワークという 独自フレームワークに近いものにインストールしていきます。

さて導入から躓いた場所、解決策を書いていきます。

インストールはComposerで行います。

{
    "require-dev": {
        "phpunit/phpunit": "3.7.*",
    "phpunit/dbunit": ">=1.3,<1.4",
        "phpunit/phpunit-skeleton-generator": "*",
    "piece/stagehand-testrunner": "~4.1@dev",
    "davedevelopment/phpmig": "*"
    }
}

今回はvendor以下にインストールを行いました。

次に設定周りです。

PHPUnitではphpunit.xmlを指定することによって 色々な設定ができます。

今回目的としては

「独自フレームワークでDBにテストデータを入れつつ、正常系、異常系のテストを行う」

です。

今更ですがテストの大まかな流れとしては

・必要データをDBに投入 ・必要引数の設定と返り値における期待値を設定 ・methodに問い合わせ期待値と一致するか確認

上記を自動化してくれます。

さて、続きです。

ということで目的からするに必要な情報としては 既に作ってあるmethodを見に行くのでdefineされているPathの読み込みや DBのユーザ、ホスト情報等が必要になってくると思います。 それを設定するのがphpunit.xmlです。

ということで作っていきます。

<phpunit colors="true" verbose="true" bootstrap="[APP_PATH]/mwf/vendor/phpunit/UnitTestEntry.php">
    <php>
        <var name="DB_DSN" value="mysql:dbname=[DBName];host=[HOST]" />
        <var name="DB_USER" value="[DB_USER]" />
        <var name="DB_PASSWD" value="[DB_PASSWD]" />
        <var name="DB_NAME" value="[DB_NAME]" />
        <env name="ENV" value="[ENV]" />
        <ini name="display_errors" value="on" />
    </php>
    <testsuites>
        <testsuite name="Unit Test">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

こんな感じで設定しました。 上から見て行きましょう。

[bootstrap] ここには初期に読み込ませるフレームワークのbootstrapやentryのファイルpathを指定します。

[DB_DSN] mysql:dbname=[DBName];host=[HOST] 上記の形でDB名とホストを指定します。

[DB_USER][DB_PASSWD][DB_NAME] DBのユーザ名パスワード、DBの名前を指定します。

[ENV] Unitテストを走らせる上で設定上色々と変更する場合があるので 環境変数にはPHPUnit等の環境変数を指定します。 これによって後で記述しますが、ConfigやDBManager等に追加が必要です。

さて早速動かそうと思ったところで問題が。 skelgenでテストを作ろうと思ったのだが、mwfフレームワークで引き継ぎを行ったソースの entry.phpに書かれているソースが以下のようなものだった。

define( 'APP_ROOT',          \realpath( \dirname( $_SERVER[ 'SCRIPT_FILENAME' ] ) . '/..' ) );

そうAPP_ROOTが実行した場所のpathを返すようになっている。 このままだと実行phpunit自体のpathが取得され、動かない。 だからといって変更するとプログラム全体のPathが変わり、動かなくなる可能性がある。

それは困るので、entry.phpをこちらで自作してphpunitを動かす時にそちらを読み込むように変更する。 そこで使うのがbootstrapだ。 phpunit.xmlに自作したphpunit用のentry.phpを読み込ませる。

そして無事テストケースが出来上がった。 テストを行う前に、まずDBUnitの設定が必要になる。

要はテスト用に使うテーブルを通常使っているDBと分けてテスト専用DBを使うというところ。 こちらは以下のようなファイルを作って対応した。

また、DBに入れるデータについてはyamlを使うようにした。 もちろんPHPUnitではXMLjsonでのデータも作成できる。

見やすいからymlがいいのかねって話でそうなったんだけどね。

CommonDatabaseTest.php

<?php

require_once "PHPUnit/Extensions/Database/TestCase.php";
require_once "PHPUnit/Extensions/Database/DataSet/YamlDataSet.php";

class CommonDatabaseTest extends PHPUnit_Extensions_Database_TestCase
{

    /**
     * @var PDOのインスタンス生成は、クリーンアップおよびフィクスチャ読み込みのときに一度だけ
     */
    static public $pdo = null;

    /**
     * @var PHPUnit_Extensions_Database_DB_IDatabaseConnection のインスタンス生成は、テストごとに一度だけ
     */
    public $conn = null;

    /**
      * データベースに接続
      */
    public function getConnection()
    {
        if ( $this->conn === null ) {
            if ( self::$pdo == null ) {
                self::$pdo = new PDO($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']);
                self::$pdo->query('SET NAMES UTF8');
            }
            $this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_NAME']);
        }

        return $this->conn;
    }

    /**
      * フィクスチャを読み込んで、データベースを初期化する
      *
      * @return PHPUnit_Extensions_Database_DataSet_CompositeDataSet
      */
    public function getDataSet($fixtureFile)
    {
        $fixturePath = APP_ROOT . '/tests/fixtures/' . $fixtureFile;
        return new PHPUnit_Extensions_Database_DataSet_YamlDataSet($fixturePath);
    }
}

PHPUnit_Extensions_Database_TestCaseを継承し、CommonDatabaseTestを作りました。 自分の実装はこちらのファイルに記述するようにします。 今後作るテストファイルもこのCommonDatabaseTestを継承して作るようにします。

getConnectionにはphpunit.xmlで指定した内容が入り getDataSetではfixtureFileのpathを引数に入れてmethod毎にfixtureのファイルを用意するようになる。 要はそのmethod毎にテーブルの出し入れをしつつ、柔軟なテストができるということ。

ここで簡単にディレクトリ構造だけを書いておく。

APP_ROOTとなるディレクトリに対して testsというディレクトリを作った。 この中にあるファイルを読み込んでテストを行うようにする。 また、その他fixtureについてもここに入れるルールとした。

tests以下は以下の通りである。

[root@localhost tests]# tree
.
├── fixtures
│   └── TestDaoModelTest             // ディレクトリ名はテストするファイル名
│       ├── testGetAll.yml           // method毎にymlファイルを作成
│       ├── testGetTestById.yml
│       └── testTestTest.yml
├── model
│   └── dao
│       └── TestDaoModelTest.php     // テストが書かれているファイル
└── sql                              //テストで必要となるだろうテーブルはここに置いておく
    ├── create_table.sql
    └── test.sql

説明はコメントに書いた通りなんだけど、用意するのは大まかに2つ テストに必要なfixtureファイルと実際にテストを行うファイル SQLについては便利かもしれないと思って構築用に用意しているものである。

PHPUnitを動かすとDB内の該当テーブルをTRUNCATEし、対象のfixtureファイルをINSERTしてくれる。 なのでテーブルだけ作っておけばOKなのである。

さてこれで終わりかと思いきや、先ほど作ったCommonDatabaseTest.phpにあるように fixtureファイルを渡してあげる必要がある。 そこについてはテストファイルのsetUpでファイル名とmethod名を取得してあげ、渡すようにした。

TestDaoModelTest.php

<?php

require_once VENDOR_ROOT . '/phpunit/dbunit/CommonDatabaseTest.php';
require_once VENDOR_ROOT . '/phpunit/dbunit/PHPUnit/Extensions/Database/DataSet/YamlDataSet.php';
require_once SCRIPTS_ROOT.'/models/dao/TestDaoModel.class.php';

class TestDaoModelTest extends CommonDatabaseTest
{

    /**
     * @var TestDaoModel
     */
    protected $testDaoModel;
    protected $fixtureFile;


    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $fileInfo = pathinfo(__FILE__);
        $this->fixtureFile = $fileInfo['filename'] . '/' . $this->getName() . '.yml';
        parent::setUp($this->fixtureFile);
        $this->testDaoModel = new TestDaoModel();
    }

    /**
     * Tears down the fixture, for example, closes a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown()
    {
        parent::tearDown($this->fixtureFile);
    }

    /**
     * @covers TestDaoModel::getAll
     */
    public function testGetTestById()
    {
        $data = [
            'Id' => 200,
            'name' => "Bad Request"
        ];

        $result = $this->testDaoModel->getTestById();
        $this->assertEquals($data, $result, 'error');

    }

    /**
     * @covers TestDaoModel::getAll
     */
    public function testGetAll()
    {
        $testDaoModel = new TestDaoModel();
        // Remove the following lines when you implement this test.
        $data = [
            0 => [
                "id" => 1,
                "name" => "test1",
                "created" => "2015-03-10 11:00:00",
                "modified" => "2015-03-11 11:00:00",
                "deleted" => 0
            ],
            1 => [
                "id"=> 2,
                "name"=> "test2",
                "created"=> "2015-03-10 11:00:00",
                "modified"=> "2015-03-11 11:00:00",
                "deleted"=> 0
            ],
            2 => [
                "id"=> 3,
                "name"=> "test3",
                "created"=> "2015-03-10 11:00:00",
                "modified"=> "2015-03-11 11:00:00",
                "deleted"=> 0
            ]
        ];
        $result = $testDaoModel->getAll();
        // $result = false;
        $this->assertEquals($data, $result, 'error');
        // $this->assertEquals(false, $result, 'error');
    }
}

上記のような形のテストファイルを用意した。

ここでまずは先ほどのfixtureファイルの読み込み部分を見ていく。

    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $fileInfo = pathinfo(__FILE__);
        $this->fixtureFile = $fileInfo['filename'] . '/' . $this->getName() . '.yml';
        parent::setUp($this->fixtureFile);
        $this->testDaoModel = new TestDaoModel();
    }

まずテストが走る前にsetUpが走る。 ここでpathinfo関数を使ってファイル名を取得し、$this->getName()を使ってmethod名を取得している。 これで$this->fixtureFileに対してpathを格納する。 そしてparent::setUpに引数をとして渡してある。 また、毎回クラスのインスタンスを作るのは面倒なので最後にインスタンスを作成させる。

parent::setUpは以下のファイルへ続く

vendor\phpunit\dbunit\PHPUnit\Extensions\Database\TestCase.php

    /**
     * Returns the test dataset.
     *
     * @return PHPUnit_Extensions_Database_DataSet_IDataSet
     */
    protected abstract function getDataSet($fixturePath);


    /**
     * Performs operation returned by getSetUpOperation().
     */
    protected function setUp($fixturePath)
    {
        parent::setUp();
        $this->databaseTester = NULL;
        $this->getDatabaseTester()->setSetUpOperation($this->getSetUpOperation());
        $this->getDatabaseTester()->setDataSet($this->getDataSet($fixturePath));
        $this->getDatabaseTester()->onSetUp();
    }

ここのsetUpが呼ばれるのでsetDataSetを行っている$this->getDataSetへpathを渡す 更に同じファイル内にあるgetDataSetへも引数として渡してあげる すると、先ほど作ったCommonDatabaseTestのgetDataSetへと渡す事ができる

さてこれでPHPUnit等の設定はある程度完了です。

次にphpunit.xml環境変数を設定しました。 実際に接続にいくときはテストファイルではなく、テスト対象のファイルにアクセスし、そちらのmethodを使うので そちらの接続先を変更する必要があります。

フレームワーク側のDatabaseManager等に各環境ごとの設定が書かれていると思うので そちらにDBの接続情報を追加し、テスト用のDBへ向けてあげるようにします。

これで大体の設定は完了

実際に以下で実行をします。

$ phpunit tests/

これで走らせたのですが、うまく走らなかったです。 ※正直install後にこのブログを書いているのでちょっと他にも設定必要だったかもしれないのですが・・・

さて、何故走らないかというと。 走ってはいるんです。 ただ、テストの途中で止まるのです。

今回作成したテストファイルについては

testGetTestById このmethodではjsonファイルから情報を引っ張りだしているだけです。

testGetAll 問題だったのはこれ。 DBに入っているtestテーブルの内容を全て引っ張り出すという処理です。

処理的には動くのですが、テストの途中でwaitがかかったようにうんともすんとも言わないのです。

PHPUnitに問題があるのか? 独自で実装したyml読み込みが悪いのか? 何か設定が悪いのか?

悩みに悩んでPHPUnitのソースを追っていく事に。

実行した所から処理が流れるところを確認してどうやらPHPUnitは正常な処理をしているはずが TruncateしようとしてるところでMySQLのテーブルがロックされているような挙動が見られました。

まずはMySQLに対してPHPUnitはどういったQueryを投げているか調べました。

MySQLのプロセスリストをリアルタイムで見たかったので以下のコマンドで覗いてみました。

$ watch -n1 'mysql -uroot -e "show full processlist"'

すると2つのプロセスが立ち上がり、一つはstatus sleepとなっており、やはりロックされている状態でした。 にしても何故2つもプロセスが立あがるのだろうと次はQueryを調べることに。 MySQLの設定でQueryログを出すように設定をしました。

/etc/my.cnf

[mysqld]
general-log=TRUE
general-log-file=/var/log/mysql/query.log

上記を追加して/var/log以下にmysqlディレクトリを作成してQueryログを出力することに。

そしてMySQL再起動後、PHPUnitを実行してみると以下ログが出てきました。

27 Connect   root@localhost on
27 Query     select @@version_comment limit 1
27 Query     show full processlist
27 Quit
28 Connect   test@localhost on test
28 Query     SET NAMES UTF8
28 Query     TRUNCATE `test`
28 Query     SHOW COLUMNS FROM `test`
28 Query     SHOW INDEX FROM `test`
28 Query     INSERT INTO `test` (`id`, `name`, `created`, `modified`, `deleted`) VALUES (NULL, 'test1', '2015-03-10 11:00:00', '2015-03-11 11:00:00', '0')
28 Query     INSERT INTO `test` (`id`, `name`, `created`, `modified`, `deleted`) VALUES (NULL, 'test2', '2015-03-10 11:00:00', '2015-03-11 11:00:00', '0')
28 Query     INSERT INTO `test` (`id`, `name`, `created`, `modified`, `deleted`) VALUES (NULL, 'test3', '2015-03-10 13:00:00', '2015-03-11 13:00:00', '0')
28 Query     TRUNCATE `test`
28 Query     SHOW COLUMNS FROM `test`
28 Query     SHOW INDEX FROM `test`
28 Query     INSERT INTO `test` (`id`, `name`, `created`, `modified`, `deleted`) VALUES (NULL, 'test1', '2015-03-10 11:00:00', '2015-03-11 11:00:00', '0')
28 Query     INSERT INTO `test` (`id`, `name`, `created`, `modified`, `deleted`) VALUES (NULL, 'test2', '2015-03-10 11:00:00', '2015-03-11 11:00:00', '0')
28 Query     INSERT INTO `test` (`id`, `name`, `created`, `modified`, `deleted`) VALUES (NULL, 'test3', '2015-03-10 11:00:00', '2015-03-11 11:00:00', '0')
29 Connect   test@localhost on test
29 Query     set autocommit=0
29 Query     SELECT * FROM test
28 Query     TRUNCATE `test`

ID27 についてはshow full processlistを実行しているだけなので置いといて ID28 がPHPUnitでfixtureを入れているログになります。 そしてID29 が実際テストファイルからテスト対象methodを呼び出して問い合わせしているところです。

注目すべきはset autocommit=0というところです。

このコマンドをフレームワーク側で投げていて、autocommitを無効にしていました。

これによって一番最後のTRUNCATE testが実行できず止まっていたようでした。

実際フレームワークにてautocommit無効処理を見つけ、phpunit.xmlに記載している ENVだった場合はその処理を行わないように書き換えました。

これにより無事UnitTestが可能に!

いやーハマった。

これから正常、異常系のテストをどう書くか memcacheやrandを使った時のテストをどうするか等 まだまだ問題は山積みですが、少しずつ解決していこうと思います。

テストないコードは書いていて気持ち悪いし、debugもしずらいので これで晴れてテストし、品質を保持した開発ができるっ!

MWFフレームワークを使った開発をしている人は少ないとは思いますが 独自フレームワークやテストの機構を持っていないフレームワークは数多あると思います。 そんな人の役に立てれば。

さーて次のタスクいくかー

【Git】 GithubにSSHの公開鍵を登録する

まぁ最近SVNじゃなくてGitだよね。 ってことでGit導入してみたはいいけど、SSHで鍵登録とかちょっと頭こんがらがりそうで って最初のうち結構そう思ってた。

公開鍵?秘密鍵

わからんよねー。

公開鍵とか秘密鍵っていうのは鍵交換方式認証で使うファイルで 秘密鍵のファイルってのは自分にしか見られないようにするのが鉄則。 公開鍵は認証を行うサーバに置いとくもの。

この2つの鍵がペアになっていることによってIDとパスワードの代わりをしてくれる。

ってことで早速登録していきましょう。

まずは自分のサーバ側 今回は今まで立てたVirtualBoxのローカルサーバ上で行いました。

まずは~/.sshがあるかないか。 なければ作っていきましょう。

# ssh-keygen -t rsa -C "mail@example.com"

mail@example.comはGitに登録したアドレスでも入れておいてください。 そうすると3つほど聞かれます。

保存場所

パスワード

パスワード(確認)

今回パスワードは飛ばしました。なのでEnter3回タタターンしました。

そうすると2つのファイルが~/.ssh以下にできます。

id_rsa             id_rsa.pub

この2つのファイルが公開鍵と秘密鍵になります。

まずはGithubの設定から「SSH Keys」を選択して「Add SSH key」をします。

TitleとKeyというのが出てくるのでTitleはわかりやすい名前を任意でつけます。

Keyには先ほど作ったファイルのid_rsa.pubをless等で表示させて、中のコードをすべてコピーして貼り付けます。

完了したらAdd key

これだけです。

ただ、これだけだと本当に登録されて疎通できるかわからないので確認しましょう。

# ssh -T git@github.com

少し待つと以下の文が出てくるので「yes」と打ちます

The authenticity of host '[ssh.github.com]:443 ([192.30.252.148]:443)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)?

以下文が表示されれば接続成功です。

Hi xxxx! You've successfully authenticated, but GitHub does not provide shell access.

ただ、繋がらない場合もあります。 一つは鍵作成の時にパスワードを登録した場合 この場合は以下コマンドを実行することによりパスワードを回避できます。

ssh-add ~/.ssh/id_rsa

また、以下のような文が表示され、port 22が使えない場合があります。

ssh: connect to host github.com port 22: Connection timed out

この場合は以下コマンドでportを変更して接続してみてください。

ssh -T -p 443 git@ssh.github.com

これで正常に接続できた場合、443portを使って接続するように設定ファイルを作成します。

# touch ~/.ssh/config
# vi ~/.ssh/config

configには以下設定を書きます。

Host github.com
    User git
    Hostname ssh.github.com
    Port 443
    IdentityFile ~/.ssh/id_rsa

これで接続ができるようになります。

Githubは公式に色々な解決法があるので見ておくといいかもしれないですね。

【MySQL】データベース内のテーブル容量を表示させるquery

今回はMySQLです。 そろそろ環境構築も落ち着いてきて、さてさてソースでも読もうかなって思っている時に、ソースだけじゃわからないようなレガシーなコードなわけですよ。 PHPなんですが$dataだったり、どういう値が入っているかコメントも書かれておらず、マジックナンバーガリガリな感じなんです。 そして併用するのがDB。 こちらについてはテーブル数が100ちょっとで使われてたり使われてなかったり。

直感的ではないプログラムはそんな好きじゃないです。

どのDB使われてるんだろう。 多分これだろうと思ってselectかけてみるもハズレ(ハズレってなんだ…)

そんな時たまに使うのがデータベースの容量からテーブルの容量を出力できるSQLなのです。

今回はどのテーブルにどのくらいの情報が入っているのかを確認するためのSQLになります。

とりあえず適当に作ってあったMySQLのDBはこんな感じです。

mysql> show tables;
+------------------+
| Tables_in_test2  |
+------------------+
| area_code        |
| detail_area_code |
| prefectural_code |
| shop_image       |
| shop_info        |
| test_table       |
| user             |
+------------------+
7 rows in set (0.00 sec)

この中で使っていないテーブがあるのです。

そんな時こんなコマンド打ちます。

select 
table_name, engine, table_rows as tbl_rows, avg_row_length as rlen, 
floor((data_length+index_length)/1024/1024) as allMB,  #総容量
floor((data_length)/1024/1024) as dMB,  #データ容量
floor((index_length)/1024/1024) as iMB   #インデックス容量
from information_schema.tables 
where table_schema=database() 
order by (data_length+index_length) desc;

このselect文を発行するとこんなのが返ってきます。

+------------------+--------+----------+------+-------+------+------+
| table_name       | engine | tbl_rows | rlen | allMB | dMB  | iMB  |
+------------------+--------+----------+------+-------+------+------+
| test_table       | InnoDB |        0 |    0 |     0 |    0 |    0 |
| user             | InnoDB |        4 | 4096 |     0 |    0 |    0 |
| shop_info        | InnoDB |        6 | 2730 |     0 |    0 |    0 |
| shop_image       | InnoDB |        0 |    0 |     0 |    0 |    0 |
| prefectural_code | InnoDB |       47 |  348 |     0 |    0 |    0 |
| detail_area_code | InnoDB |        2 | 8192 |     0 |    0 |    0 |
| area_code        | InnoDB |       68 |  240 |     0 |    0 |    0 |
+------------------+--------+----------+------+-------+------+------+
7 rows in set (0.04 sec)

データ少なすぎてMB単位の表示だと0になっちゃうのですが。。。

tbl_rowsは見れば分かる通りですね。 ここが0になっているテーブルはデータがないものです。

これがあればどのテーブルが使われていてどのテーブルが使われていないかすぐわかりますね。

実はわかったところまではよかったんですが、カラム名がvalueだとかdataって書いてあって何が入ってるか全然わからないんですけどね。 長い戦いになりそうだ。。。

【PHP】Apacheのaccess_logにユーザIDを追加する

最近プログラムから離れてるけど大丈夫かなぁ。

ってことで今日もサーバいじいじしています。

いや、暇なんじゃないんです。

今日はApacheのログについてです。

モバイルゲームだったりだとか会員制サイトだったり、 ユーザIDが存在するサービスではKPI用にログが必要だったりしますよね。

どのユーザがいつ、どこのURLにアクセスしたのか。

そういうのをアクセスログから取ったりしてユーザの動向を探ったり、してサービス改善をするものです。

さて今回依頼されたのはまさにそれ。 どのユーザがどこのページにいつアクセスしたのか取りたいんだけどなんとかならない?

という内容でした。

もちろんユーザIDだけでなく、任意の値を設定することができます。

通常apacheを使っているとaccess_logが出力されており、 リクエスト時刻やステータスコード、User-Agentなんていうものが出力されています。

要はアクセスログにユーザIDくっつけて出力しちゃえば簡単に取れるんじゃないかと。

今回はPHPです。

Frameworkは特に指定しませんが、filterやAuthなど適切な場所でapache関数を使い、ログのフォーマットに出力の指定を行います。

apache関数はいくつかありますが設定する関数は2つ。

apache_setenvとapache_noteがあります。

apache_note

apache_note — Apacheリクエスト記号(note)を取得/設定する。 この関数は、Apache の table_get および table_set のラッパーです。 リクエスト中に存在す>る note のテーブルを編集します。 このテーブルは、Apache モジュール間の通信に用いるものです。

apache_setenv

apache_setenv — Apacheサブプロセスの環境変数を設定する apache_setenv() は variable で指定された Apache 環境変数の値を設定します。

見た感じだとapache_noteのほうが今回の件に関しては適切かな? setenvって何か設定値を環境せ変数に設定したりするイメージ。

ということでamache_noteを使います。

apache_note('X-User-ID', $userId);

こんな感じで書いてあげます。

さらにApacheのlog出力設定部分のフォーマットに以下を追加

# %{X-User-ID}nを追加
 LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\" %{X-User-ID}n" web-combined

設定後はapacheの再起動を忘れないように。

# service httpd restart

こうすることによってアクセスログにユーザIDを追加することができました。

【Linux】CentOS6環境にPHPUnitをインストール

さて自分のプログラムもそうなんだけどテスト駆動開発って大切ですよね。 工数かかるし、テストやってると時間ねえよって状態なのは重々承知なのですが これやるだけでmethod単位で動かせてコードを担保できるのはでかいです。

実際にはテストファーストでやるものですが、どうしても正直なところ コードを書いてからバグないかなぁとかっていってテスト書いていく事が多いです。 そこはちゃんとしないとなぁと思いつつもやはり開発に工数をかけられない場合は。。。 まぁそれは別ですね。

今回はそんなテストツールの中でもPHPUnitのインストール方法です。 PHPが入っていれば簡単なんですよ。

使い方はまた別途書きますが、まずはインストールですからね。

まずはPHPがちゃんと入っているか確認。

# php -v
PHP 5.3.3 (cli) (built: Oct 30 2014 20:12:53) 
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

今の環境には5.3.3が入ってるみたいですね。

入っていなければ必要なPHPyumでさくっと入れちゃってください。

さてPHPUnitっていうのはPHP標準ライブラリ仕様のPEARに入っているので PEARからインストールします。

# pear channel-discover pear.phpunit.de
Adding Channel "pear.phpunit.de" succeeded
Discovery of channel "pear.phpunit.de" succeeded

続いて

# pear install phpunit/PHPUnit
downloading PHPUnit-4.0.18.tgz ...
Starting to download PHPUnit-4.0.18.tgz (2,981 bytes)
....done: 2,981 bytes
install ok: channel://pear.phpunit.de/PHPUnit-4.0.18
[root@localhost html]# phpunit -v
You have installed PHPUnit via PEAR. This installation method is no longer
supported and http://pear.phpunit.de/ will be shut down no later than
December, 31 2014.

Please read http://phpunit.de/manual/current/en/installation.html and
learn how to use PHPUnit from a PHAR or install it via Composer.

ん?なんか言われてる。

とりあえずバージョン確認

# phpunit --version
You have installed PHPUnit via PEAR. This installation method is no longer
supported and http://pear.phpunit.de/ will be shut down no later than
December, 31 2014.

Please read http://phpunit.de/manual/current/en/installation.html and
learn how to use PHPUnit from a PHAR or install it via Composer.

ふむ、調べた結果PEARじゃなくてComposerで落とせと。

更に調べるとPHPは5.3.3以上を推奨しているということで、まずはPHPのバージョンを5.5にあげます。 上げ方も別途かなぁ。 めんどくさいかもしれないけど、このブログにある

複数PHPバージョンを管理するphpbrew

http://deadcode.hatenablog.jp/entry/2014/05/20/162923

これみてくれればと思います。

とりあえずの環境なのでさくっと自分は5.5.19まであげちゃいました。

# php -v
PHP 5.5.19 (cli) (built: Nov 16 2014 09:53:48)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2014, by Zend Technologies
    with Xdebug v2.2.6, Copyright (c) 2002-2014, by Derick Rethans

とりあえずテスト用のPHPを用意しているのでそいつと同じディレクトリに一旦落とすために そのファイルがあるディレクトリに移動。

PHPUnitについて最新版はここで確認してください。

https://packagist.org/packages/phpunit/phpunit

さてComposer.jsonの中身です。 まぁ適当に4.2.1あたり入れてみます。 なんでかというと気分です。 テンキー触ってたらそうなっただけです。

{
    "name": "phpunit",
    "description": "PHPUnit",
    "require": {
        "phpunit/phpunit": "4.2.1"
    },
    "config": {
        "vendor-dir": "PHPUnit"
    }
}

そしたらwgetでcomposer.pharを持ってくる

wget http://getcomposer.org/composer.phar

そしてインストール。 インストールすればComposer.json記載のPHPUnitがinstallされる。

# php composer.phar install

何かエラーが出てしまって再度落としたいとかっていうときは composer.lockを削除して

# composer install

これでできると思います。 composer.lockファイルを消さないとinstallしてもエラー出続けます。