[Bitbucket] Webhook+PHPを利用したGitデプロイ自動化

BitbucketのWebhook+PHPスクリプトによる外部サービスを使わない簡単なデプロイ自動化をしてみます。

今回の環境は以下のような感じ。nginxですが、ApacheでもSSHキーの部分を変更すれば対応できるかと思います。

▼環境
Bitbucket
CentOS 6
nginx
php-fpm

*PHPでシェルコマンドを実行するexec()関数が使える前提

デプロイ自動化のイメージ

1. ローカルからBitbucketにpush
2. BitbucketからWebhookでリモートサーバーのdeploy.phpにリクエスト
3. deploy.phpがexec()でgitコマンドを実行
4. git fetch && git checkoutで更新を反映

ローカル → Bitbucket → リモートサーバー

サーバーにGitレポジトリをクローン

$ mkdir /git
$ cd /git
$ git clone --mirror git@bitbucket.org:accountname/myproject.git

git clone –mirror とするとリポジトリのミラーが作成されます。

$ cd /git/myproject.git
$ GIT_WORK_TREE=/var/www/html git checkout -f

WordPressのテーマだけホストする場合は、GIT_WORK_TREEを/var/www/html/wp-content/themes/twentyfourteenというようにします。

デプロイ用のPHPスクリプトを作成

まずデプロイ用のPHPスクリプトを作ります。PHPでシェルコマンドを実行するので、exec()関数使います。

実際にexec()で実行するシェルコマンドは以下のような感じ。

$ cd /git/myproject.git/
$ git fetch
$ GIT_WORK_TREE=/var/www/html/ git checkout -f

実際のPHPスクリプトは以下の通りです。今回は、ログ出力もなしのでミニマムのスクリプトを紹介します。

$git_path, $www_path, $secret_keyを環境に合わせて変更してください。

//GitとWebのディレクトリへのフルパス
$git_path = '/git/myproject.git/';
$www_path = '/var/www/html';

//example.com/deploy.php?key=以下の任意のキー
$secret_key = 'YOUR_SECRET_KEY';

//keyパラメータと$secret_keyが合っていればexec()
if ($_GET[key] === $secret_key)  {
    $command = "cd {$git_path} && git fetch && GIT_WORK_TREE={$www_path} git checkout -f";
    exec($command);
}

/var/www/html以下にdeploy.phpとして保存します。

$ vi /var/www/html/deploy.php

nginxのSSHキーをssh-keygenで作成

リモートサーバーのSSHキーをBitbucketに登録します。

外部(Bitbucket)からのHTTPリクエストを受けて、deploy.phpが実行されるのでSSHキーはPHPを実行するnginxのものを作ります。

もしApacheを使っている場合は、Apacheに置き換えればOKかと。

sshキーの置き場所は、Apacheのデフォルトと同じ/var/www/.sshにします。

nginxのデフォルトだと/var/cache/nginx/.sshになっていて、キャッシュ用ディレクトリのせいなのか2度ほど消えてしまったことがあるためです。

$ mkdir /var/www/.ssh
$ chmod 777 .ssh
$ sudo -u nginx ssh-keygen
Enter file in which to save the key (/var/cache/nginx/.ssh/id_rsa):

とでるので、/var/www/.ssh/id_rsaと入力してEnter。パスワードは空のままなのでEnterします。

Generating public/private rsa key pair.
Enter file in which to save the key (/var/cache/nginx/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/cache/nginx/.ssh/id_rsa.
Your public key has been saved in /var/cache/nginx/.ssh/id_rsa.pub.
The key fingerprint is:
aa:jd:87:40:58:cn:as:jd:73:8f:a0 nginx@example.com
The key's randomart image is:
+--[ RSA 2048]----+
|          .      |
|         .       |
|     . + *       |
|    .   S o      |
|    = . = .  oE  |
|       .   .=.+o |
|        S...o= oo|
+-----------------+

SSHキーは/var/www/.ssh/に作成されました。

パーミッションを変更しておきます。

$ chown -R nginx:nginx .ssh
$ chmod 700 -R .ssh

あとはSSHを登録。

$ ssh-agent /bin/bash
$ ssh-add /var/www/.ssh/id_rsa

登録されていることを確認します。

$ ssh-add -l
  2048 aa:jd:87:40:58:cn:as:jd:73:8f:a0/var/www/.ssh/id_rsa (RSA)

Bitbucketのデプロイキー登録&Webhookの設定

$ cat /var/www/.ssh/id_rsa.pub

Settings > デプロイ鍵 > 鍵を追加をクリック。catした文字列をコピペ。

bitbucket-auto-deploy4

さらに、Webhookを設定します。Settings > Webhooks > Add webhookをクリック。

タイトルは、「Deploy to exmaple.com」など好きなタイトルにして、URLは「http://example.com/deploy.php?key=SECRET_KEY」を入力します。

bitbucket-auto-deploy3

フックとなるアクションはpush以外にもmergeなどもあるので、開発サーバーのフックはdevelopブランチへのpush、本番サーバーのフックはmasterブランチへのmergeなど分けることもできます。

パーミッションを変更

/gitのパーミッションと/var/www/.sshを変更します。

$ chown -R nginx:nginx /git/
$ chown nginx:nginx /var/www/html/deploy.php

ssh_configの編集

nginxのsshキーの場所をデフォルトから変更したので、ssh_configを編集します。

~/.ssh/configはユーザー用、ssh_configはシステム共通のsshの設定ファイルです。

$ vi /etc/ssh/ssh_config

以下の内容を追記します。

Host bitbucket.org
  User git
  IdentityFile /var/www/.ssh/id_rsa

ローカル開発環境からpush

最後にローカル開発環境からpushします。README.mdなど好きなファイルを編集して、pushしてください。

$ git add README.md
$ git commit -m 'change README.md
$ git push -u origin master

あとは、サーバー側も更新されていればOK!ファイルサイズによっては、デプロイに時間がかかる場合もあります。

BitbucketのWebhookが動かない場合のトラブルシューティング

以下の順で確認していきます。

1. Webhookが200を返しているか
2. PHPスクリプトは間違っていないか
3. ファイルパーミッションの確認
4. デプロイキーが正常に登録されているか
5. exec()でgitコマンドが実行されない

1. Webhookが200を返しているか

Bitbucketのレポジトリ > Settings > Webhook からView requestsをクリック。

bitbucket-auto-deploy1

Webhookのリクエスト履歴が見れるので、右端のステータスコードが200になっていることを確認。

bitbucket-auto-deploy2

2. PHPスクリプトは間違っていないか

サーバーにssh接続して、ターミナルから直接PHPを実行してみます。これで更新が反映されているようなら、PHPには問題ないはずです。

$ cd /var/www/html/
$ php deploy.php

3. ファイルパーミッションの確認

ファイルパーミッションが正しく設定されているか確認します。ls -laで確認するか、chown、chmodコマンドを再実行してみてください。

4. デプロイキーが正常に登録されているか

そのユーザーのssh鍵の設定が正しく行われているか確認します。実際にnginxユーザーになってgit fetchして確認するのが簡単なのでやってみます。

$ su -s /bin/bash nginx
$ cd /git/myproject.git
$ git fetch

ついでに、nginxユーザーでphp deploy.phpをして正常に動くことも確認しておくと良いと思います。

5. exec()でgitコマンドが実行されない

意外と落とし穴なのが、HTTPリクエストで実行されるPHPスクリプトがnginxじゃなくてApacheだったといった単純な罠。

これを確認するために、シェルコマンドのwhoamiを利用します。/var/www/htmlにtest.phpなどを作って以下をコピペ。

echo exec("whoami");

http://example.com/test.phpにブラウザからアクセスするとPHPスクリプトがどのユーザーで実行されたのか表示されます。

もし実行結果がapacheとなっていれば、service nginx statusとservice httpd statusでnginxが起動していて、apacheが止まっていることを確認。

さらに、/etc/php-fpm.d/www.confを開いてuserとgroupがnginxになっていることを確認します。

user = nginx
group = nginx

ここまで確認できればある程度のエラーには対応できるかなと思います。

Laravel4をCentOS6+nginx+PHP5.4でインストールする

最近人気があるLaravelを使うためのメモです。

【環境】
CentOS release 6.7 (Final)
PHP 5.4.44

composerからLaravelをインストール

composerを入れてない場合は以下のコマンドでインストール。

$curl -sS https://getcomposer.org/installer | php
$mv composer.phar /usr/local/bin/composer

その後、以下のコマンドでLaravelを入れます。PHPのバージョンが5.4だったので、最新のLaravel5ではなくLaravel4(日本語注釈付き)にします。

$composer create-project "laravel-ja/laravel=~4.2" your_app --prefer-dist

Mcrypt PHP extension required.が発生した場合

composerでLaravelを入れる時に以下のようなエラーが出ることがあります。これはPHP拡張機能である暗号化モジュールmcryptがPHPに入っていないために発生するようです。

  Mcrypt PHP extension required.
  Script php artisan clear-compiled handling the post-install-cmd event returned with an error

  [RuntimeException]
  Error Output:

yumでlibmcryptとphp-mcryptを入れます。

$yum --enablerepo=epel install libmcrypt
$yum --enablerepo=remi update
$yum install --enablerepo=remi php-mcrypt

PHPのバージョンや環境によっては上手くいかないこともあります。

どうしてもエラーが解決できない場合は、PHPをソースからビルドする時に「$./configure –with-mcrypt」のように–with-mcryptパラメータをつけることで拡張機能を入れることもできるようです。

mcrypyを入れたら以下のコマンドで「mcrypt support => enabled」となればOKです。

$php -i | grep -i mcrypt

nginxを使っているので、php-fpmをリスタートをしてmcryptを有効化させます。

$service php-fpm restart

nginxでLaravelを動かす

まず、app/storageのパーミッションを変更しておきます。

$chmod -R 777 app/storage

すでにnginxを利用している場合は、rootをLaravelのpublicディレクトリに向けるだけでOKです。

$vi /etc/nginx/conf.d/your_app.conf
  root /var/www/html/your_app/public;
$service nginx restart

URLにアクセスし「You have arrived.」と表示されていれば完了です。

あとはapp/config内のapp.phpやらdatabase.phpを設定すれば開発に取り掛かれそうです。

PHPで文字化けせずにCSVファイルを読み込む方法

CSVを読み込むと「文字化けしている…」ってことがよくありますよね。そんな時の対処法です。

文字化けせずにCSVファイルを読み込む

まず、SplFileObjectでCSVファイルを読み込みます。

fgetcsv()でもいいのですが、参考にさせて頂いた「【PHP】その CSV 変換、本当に「fgetcsv」でいいの?」によると、一番速くメモリの使用量も少ないのはSplFileObjectだそうなのでコレを使います。

また、エンコーディングは文字列ではないのでmb_convert_encoding()ではなく、mb_convert_variables()を使ってUTF8にエンコードします。

/**
* CSVから配列を作成する関数
*
* @param $csv string CSVファイルのパス
* @return $array array UTF8エンコード済の配列
*/
function csv_to_array($csv) {
  $file = new SplFileObject($csv);
  $file->setFlags(SplFileObject::READ_CSV);
  $array = array();
  foreach ($file as $line) {
    //空行はスキップ
    if (empty($line)) continue;
    $array[] = $line;
  }
  mb_convert_variables("UTF-8", array("ASCII","JIS","UTF-8","EUC-JP","SJIS"), $array);
  return $array;
}

こんな感じで読み込めます。

$data = csv_to_array("ファイル名.csv");

PHPでスクレイピング&本文抽出をする方法・ライブラリ

引用する記事のタイトル&本文の抜粋を取得したい!

ということで、PHPでスクレイピング&本文抽出する方法を調べてみました。

本文抽出に使用するライブラリ

いろいろと選択肢はあったのですが、今回はfivefilters.orgのPHP Readabilityを使ってみることにしました。

BitBucketのPHP Readabilityのレポジトリのものより、同じfivefilters.orgのFull-Text RSSレポジトリに使われているPHP Readabilityのほうが新しいっぽいのでこっちを使います。

ちなみにFull-Text RSS最新版のダウンロードは有償ですが、古いバージョンは無料で利用できます。また、APIの提供などもされているようなので気になる人は公式サイトをチェックしてみてください。

git cloneして、libraries / readability を好きなディレクトリに移動します。

$ git clone https://bitbucket.org/fivefilters/full-text-rss.git

PHP Readabilityの使い方

PHP Readabilityの使い方は簡単で、Readability.phpをrequireしてnew Readability($html, $url)とするだけです。


require_once '/PathToLibrary/readability/Readability.php';

//取得する記事URLを指定
$url = 'http://web-tsukuru.com/270';

//URLからHTMLを取得してUTF8にエンコーディング
$html = file_get_contents($url);
$html = mb_convert_encoding($html, "UTF-8", "ASCII,JIS,UTF-8,EUC-JP,SJIS" );

//tidy_parse_stringが使用可能であれば、
//tidy::cleanRepairでHTMLの誤りなどを修正
if (function_exists('tidy_parse_string')) {
  $tidy = tidy_parse_string($html, array(), 'UTF8');
  $tidy->cleanRepair();
  $html = $tidy->value;
}

//Readabilityを初期化
$readability = new Readability($html, $url);
$result = $readability->init();

if ( empty($result) ) return false;

//タイトルを取得
$title =  $readability->getTitle()->textContent;

//記事本文を取得
$content = $readability->getContent()->innerHTML;

//再度、tidy_parse_stringを使用
if (function_exists('tidy_parse_string')) {
  $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8');
  $tidy->cleanRepair();
  $content = $tidy->value;
}

タイトルの取得は、

$title =  $readability->getTitle()->textContent;

記事本文の取得は、

$content = $readability->getContent()->innerHTML;

のようにできます。

記事本文はHTMLをそのまま取得しているので、strip_tags()などでHTMLを除去すると良さそうです。

そのほかのPHPで本文抽出する方法

有名なReadabilityは本文抽出に使えるAPIを非商用で提供しているみたいです。APIだと提供終了する可能性があるので、ちょっと微妙ですが軽く使ってみるには良さそうですね。

Readability Developer APIs

そのほか、今回は使ってないですが以下のようなライブラリがあるみたいです。
https://github.com/scotteh/php-goose
https://github.com/essence/essence

また、Quoraに投げられた「HTMLから本文抽出する一番の方法は?」という質問も参考になります。

Quora | What’s the best method to extract article text from HTML documents?

PHP以外だとJavaのboilerpipeあたりが有名みたいです。

Mecabにユーザー辞書を導入する(はてな、Wikipedia、ニコニコ大百科など)

Mecabにはてなキーワード、Wikipedia、ニコニコ大百科などを元にしたユーザー辞書を追加します。公式マニュアルはこちら

ユーザー辞書を追加する流れは、

1.元データを取得してCSVファイルに変換
2.CSVファイルを辞書ファイルにコンパイル
3.Mecabの設定ファイルに追加

となります。

1.元データを取得してCSVを作成

今回は、例としてはてなキーワードを追加します。

まずは元データを取得します。

$ wget http://d.hatena.ne.jp/images/keyword/keywordlist_furigana.csv

次に「mecabにはてなキーワードのタイトルリストを追加する」を参考に以下のPHPスクリプトを実行してMecabのユーザー辞書用CSVファイルを作成します。

 $v) {
        if (preg_match("/[\",\n]/", $v)) {
            $data[$k] = '"' . str_replace('"', '""', $v) . '"';
        }
    }
    $line = implode(',', $data);
    return $line;
}

//インポート元CSVとエクスポート先CSVを引数に指定
make_csv("keywordlist_furigana.csv","hatena.csv");

なおニコニコ大百科とWikipediaについては、以下のサイトの通りすれば追加できると思います。

ニコニコ大百科データからMeCab辞書を生成する
mecabにwikipediaのタイトルリストを追加する

2.CSVからユーザー辞書を作成

下記のコマンドを実行して、csvファイルからユーザー辞書ファイル(.dic)にコンパイルします。

$ /usr/local/libexec/mecab/mecab-dict-index -d/usr/local/lib/mecab/dic/ipadic -u 辞書名.dic -f utf-8 -t utf-8 インポート元.csv

以下のコマンドで辞書を指定してmecabが使えれば問題なくコンパイルできています。

$ mecab -u 辞書名.dic

3.辞書をユーザー辞書として登録

任意のディレクトリに作成した辞書名.dicを移動します。今回は/usr/local/lib/mecab/dicにuserを作成して移動します。

$ mkdir /usr/local/lib/mecab/dic/user
$ mv 辞書名.dic /usr/local/lib/mecab/dic/user/辞書名.dic

/usr/local/lib/mecab/dic/ipadic/dicrc もしくは /usr/local/etc/mecabrc に以下を追加します。

userdic = /usr/local/lib/mecab/dic/user/辞書名.dic

これで完了です!

Mecabなど形態素解析で使うIPA品詞体系(品詞ID|pos-id)

Mecabや茶筅など形態素解析で使う辞書のIPA品詞体系をまとめてみました。

Mecabでは::getPosId()で品詞IDを取得できます。

IPA品詞体系

品詞ID 分類 説明
0 その他,間投 「あ」「ア」のみ 「そんなぁ」
1 フィラー 「えーと」「なんか」など
2 感動詞 「うむ」「お疲れさま」「トホホ」
3 記号,アルファベット 「A-z」
4 記号,一般 「?」「!」「¥」
5 記号,括弧開 「(」「【」など
6 記号,括弧閉 「 )」「】」など
7 記号,句点 「。」「.」のみ
8 記号,空白 「 」のみ
9 記号,読点 「、」「,」のみ
10 形容詞,自立 「美しい」「楽しい」
11 形容詞,接尾 「ったらしい」「っぽい」
12 形容詞,非自立 「づらい」「がたい」「よい」 「見づらい」
13 助詞,格助詞,一般 「の」「から」「を」
14 助詞,格助詞,引用 「と」のみ
15 助詞,格助詞,連語 「について」「とかいう」
16 助詞,係助詞 「は」「こそ」「も」「や」
17 助詞,終助詞 「かしら」「ぞ」「っけ」「わい」
18 助詞,接続助詞 「て」「つつ」「および」「ので」
19 助詞,特殊 「かな」「けむ」「にゃ」
20 助詞,副詞化 「と」「に」のみ
21 助詞,副助詞 「くらい」「なんか」「ばっかり」
22 助詞,副助詞/並立助詞/終助詞 「か」のみ
23 助詞,並立助詞 「とか」「だの」「やら」
24 助詞,連体化 「の」のみ
25 助動詞 「ます」「らしい」「です」
26 接続詞 「だから」「しかし」
27 接頭詞,形容詞接続 「お」「まっ」 「お高い」「まっ赤」
28 接頭詞,数接続 「計」「毎分」 数値に接続するもの
29 接頭詞,動詞接続 「ぶっ」「引き」 動詞に接続するもの。「ぶったたく」
30 接頭詞,名詞接続 「最」「総」 名詞に接続するもの。「最高値」
31 動詞,自立 「投げる」
32 動詞,接尾 「しまう」「ちゃう」「願う」 「行ってしまう」
33 動詞,非自立 「しまう」「ちゃう」「願う」 「行ってしまう」「やっちゃったね」「ご遠慮願う」
34 副詞,一般 「あいかわらず」「多分」 必ず後ろで切れるもの,連体修飾が不可能なもの
35 副詞,助詞類接続 「こんなに」「そんなに」 「する」「だ」などが後続可能な副詞.
36 名詞,サ変接続 「インプット」「悪化」 後ろに「する」「できる」などがつくもの
37 名詞,ナイ形容詞語幹 「申し訳」「仕方」 助動詞「ない」の直前に現れる名詞
38 名詞,一般 「テーブル」 普通名詞。
39 名詞,引用文字列 「いわく」のみ
40 名詞,形容動詞語幹 「健康」「安易」「駄目」 形容動詞語幹で「な」の前に現れるもの
41 名詞,固有名詞,一般 一般的な固有名詞
42 名詞,固有名詞,人名,一般 一般的な人名。
43 名詞,固有名詞,人名,姓 一般的な日本人の性。
44 名詞,固有名詞,人名,名 一般的な日本人の名。
45 名詞,固有名詞,組織 「株式会社◯◯」 組織を表す。
46 名詞,固有名詞,地域,一般 「東京」 国以外の地名。
47 名詞,固有名詞,地域,国 「日本」 国名。
48 名詞,数 「0」「一」 数字及び「何(回)」など。
49 名詞,接続詞的 「◯対◯」「◯兼◯」 単語と単語を接続するもの。
50 名詞,接尾,サ変接続 「(可視)化」 後ろに「する」がつく接尾語。
51 名詞,接尾,一般 「感」「観」「性」 複合名詞をつくるもの。
52 名詞,接尾,形容動詞語幹 「的」「げ」「がち」
53 名詞,接尾,助数詞 「個」「つ」「本」「冊」
参考:
ipadic version 2.7.0 ユーザーズマニュアル (*PDF)
形態素解析ツールの品詞体系

[PHP] HTMLに埋め込まれた相対パスを絶対パスに変換する

スクレイピングをした時に、imgタグなどで埋め込まれている画像が相対パスで指定されている場合に絶対パスに変換する関数です。

もっとスマートなやり方がありそうですが。。。ほかの人が改善してくれることを期待して公開してみます。

相対パスのパターン

相対パスのパターンは以下の7通りを想定しています。抜けているパターンがあれば教えてください。

・絶対パス

http://example.jp/sample.jpg

・http(s)の省略

//example.jp/sample.jpg

・ディレクトリ以下を指定

/images/sample.jpg

・同一ディレクトリ内

./sample.jpg

・同一ディレクトリ内

sample.jpg

・親ディレクトリ内

../../sample.jpg

・並列するディレクトリ内

../../images/sample.jpg

相対パスから絶対パスに変換するPHP関数

ベースURLと変換する相対パスを引数に指定します。

ベースURLを元に、相対パスを絶対パスに変換します。

/**
 * スクレイピングなどで画像URLを取得する時に使うために 
 * ベースURLを元に相対パスから絶対パスに変換する関数
 * 
 * @param string $target_path 変換する相対パス
 * @param string $base ベースとなるパス
 * @return $uri string 絶対パスに変換済みのパス
 */
function convert_to_uri($target_path, $base) {
  $component = parse_url($base);

  $directory = preg_replace('!/[^/]*$!', '/', $component["path"]);

  switch (true) {

    // [0] 絶対パスのケース(簡易版)
    case preg_match("/^http/", $target_path):
      $uri =  $target_path;
      break;

    // [1]「//exmaple.jp/aa.jpg」のようなケース
    case preg_match("/^\/\/.+/", $target_path):
      $uri =  $component["scheme"].":".$target_path;
      break;

    // [2]「/aaa/aa.jpg」のようなケース
    case preg_match("/^\/[^\/].+/", $target_path):
      $uri =  $component["scheme"]."://".$component["host"].$target_path;
      break;

    // [3]「./aa.jpg」のようなケース
    case preg_match("/^\.\/(.+)/", $target_path,$maches):
      $uri =  $component["scheme"]."://".$component["host"].$directory.$maches[1];
      break;

    // [4]「aa.jpg」のようなケース([3]と同じ)
    case preg_match("/^([^\.\/]+)(.*)/", $target_path,$maches):
      $uri =  $component["scheme"]."://".$component["host"].$directory.$maches[1].$maches[2];
      break;

    // [5]「../aa.jpg」のようなケース
    case preg_match("/^\.\.\/.+/", $target_path):
      //「../」をカウント
      preg_match_all("!\.\./!", $target_path, $matches);
      $nest =  count($matches[0]);

      //ベースURLのディレクトリを分解してカウント
      $dir = preg_replace('!/[^/]*$!', '/', $component["path"])."\n";
      $dir_array = explode("/",$dir);
      array_shift($dir_array);
      array_pop($dir_array);
      $dir_count = count($dir_array);

      $count = $dir_count - $nest;

      $pathto="";
      $i = 0;
      while ( $i < $count) {
        $pathto .= "/".$dir_array[$i];
        $i++;
      }
      $file = str_replace("../","",$target_path);
      $uri =  $component["scheme"]."://".$component["host"].$pathto."/".$file;

      break;
  }

  return $uri;
}

関数の実行結果

上記の関数を相対パス7パターンで実行してみます。

$base = "http://example.jp/posts/2015/06/convert_to_uri.html";
$pathes = array(
  "http://example.jp/sample.jpg",
  "//example.jp/sample.jpg",
  "/images/sample.jpg",
  "./sample.jpg",
  "sample.jpg",
  "../../sample.jpg",
  "../../images/sample.jpg"
);

foreach ($pathes as $key => $path) {
  echo convert_to_uri($path,$base)."\n";
}

実行結果は以下の通りです。なんとか上手くいった気がします。

http://example.jp/sample.jpg
http://example.jp/sample.jpg
http://example.jp/images/sample.jpg
http://example.jp/posts/2015/06/sample.jpg
http://example.jp/posts/2015/06/sample.jpg
http://example.jp/posts/sample.jpg
http://example.jp/posts/images/sample.jpg

フィードバックなどあればコメントなどにお願いします!

[APC] リロードするとFatal error: Call to undefined **となる問題

【問題】リロード時にUndefined Errorとなる

APCのオペコードキャッシュを有効化した時、サーバーを立ち上げると最初は表示されるものの、リロードすると「Fatal error: Call to undefined **」となってしまう。

apc.iniの以下の部分を「apc.enable_opcode_cache=1」とすると上記の現象になる。

; This can be set to enable the APC opcode cache
; WARNING: don't set this option if another opcode cache is enabled
apc.enable_opcode_cache=0

環境は、nginx + PHP 5.4.28(php-fpm) + APC(3.1.15dev beta)です。

【原因】APCのエラー

エラー回避の方法を調べていくと、どうやらAPCの特定バージョンで起きる不具合だったよう。詳細な再現条件は調べてないので不明ですが、APC(3.1.15dev beta)では上記不具合が起きていました。

サーバー立ち上げ時にオペコードキャッシュがAPCによって作成され、リロード時は生成されたキーを元にオペコードキャッシュを呼ぼうとするが、そこでこけてしまいFatal Errorとなるようです。

▼参照
PHP :: Bug #61219 :: method not found on 2nd request
Upgrade to PHP 5.4 from PHP 5.3 – undefined method SWIFT_License::DecodeKey()

【解決策】Zend OPCacheを導入する

パッチを当てれば直るらしいのですが、オペコードのキャッシュはZend OPCacheにすることにしました。ちなみに、Zend OPCacheはPHP5.5から導入されたものですが、PHP5.4でも問題なく使えます。

理由は、ざっくり以下の2点です。

・APCはすでに開発が止まっている
・Zend OPCacheのほうがパフォーマンスが良い(らしい)

Zend OPCache+APCの構成で解決

ただし、Zend OPCacheはユーザーデータのキャッシュには対応していないため、Zend OPCacheだけではWordPressのオブジェクトキャッシュが使えません。

そのため、APCはオペコードキャッシュには使わずにZend OPCache+APCという構成にすることにしました。

もしパッチを当てて直したいという人は、こちらのパッチを参照してみてください。

Bing Search APIで画像取得する簡易チュートリアル(PHP編)

1.登録&アカウントキーの取得

Microsoft Azure Marketplace」から、プランを選ぶ。無料で使う場合は、5,000トランザクション/月。

マイ アカウントから「プライマリ アカウント キー」を取得。

2.Bing Search APIのサンプルコード(PHP)

エラー処理などしてないですが、こんな感じで使えます。

ドキュメントは、クイックスタート(英語)パラメーターなどの一覧(英語)があります。


/**
* BingSeachAPIで画像を取得するサンプルコード
*
* @param string $keyword 検索ワード
* @return object 
*/

function search_bing($keyword) {
  //取得したアカウントキー
  $accountKey = 'aaaaaaaaaaabbbbbbbbbbbbbb';

  //エンドポイントとパラメーターなどをセット
  //画像検索以外の場合は$serviceOpを変更
  $query = urlencode("'{$keyword}'");
  $rootUri = 'https://api.datamarket.azure.com/Bing/Search';
  $serviceOp = "Image";
  $endpoint = "$rootUri/$serviceOp?\$format=json&Query=$query&ImageFilters='Aspect:Wide'";

  //ストリームコンテキストを作成
  $auth = base64_encode("$accountKey:$accountKey");
  $data = array(
    'http' => array(
    'request_fulluri' => true,
    'ignore_errors' => true,
    'header' => "Authorization: Basic $auth")
  );
  $context = stream_context_create($data);

  //とりあえず、file_get_contents()でjsonを取得
  $response = file_get_contents($endpoint, 0, $context);
  $response = json_decode($response);

  return $response;
}

3.画像検索で使うパラメーターImageFilter

「ImageFilters=’Aspect:Wide’」みたいに指定できます。

Size:Small サイズ小の画像を取得
Size:Medium サイズ中の画像を取得
Size:Large サイズ大の画像を取得
Size:Height:{Height} {Height}(数字)に指定されたサイズの画像を取得
Size:Width:{Width} {Width}(数字)に指定されたサイズの画像を取得
Aspect:Square 正方形の画像を取得
Aspect:Wide 横長の画像を取得
Aspect:Tall 縦長の画像を取得
Color:Color カラー画像を取得
Color:Monochrome モノクロ画像を取得
Style:Photo 写真を取得
Style:Graphics イラスト、グラフィックを取得
Face:Face 顔?の画像を取得
Face:Portrait 肩から上が写った画像を取得
Face:Other その他の人物写真?を取得

4. 注意点:ImageFilterのエラー

シンプルなAPIなので特に気をつける点はないのですが、ImageFilterに複数パラメーターをセットするとエラーになるのには注意です。

公式ドキュメントには、「ImageFilters=’Aspect:Wide+Size:Large’」のように複数セットできそうな記述があるのですが、実際には以下の様なエラーになります。

Parameter: ImageFilters has an invalid pattern of characters

フォーラムにも質問が書いてあるのですがどうやら仕様のようです。2012年1月に下記の質問が投げられ、それから進捗はないようなので今後の対応は望み薄な気がします。

▼参考:フォーラムでの質問
Azure Bing Search API – Image search with multiple image filters