読みがなを正しい50音順(辞書順)にソートする

読みがなを持つデータを、辞書のような順序に並べ替えたいとする。

ところが辞書の配列は意外と複雑だ。Wikipedia の索引の配列として次の規則が提示されている。

  1. 読みの五十音順とする。
  2. 清音・濁音・半濁音は、この順に並べる。
  3. 促音「っ」、拗音「ゃ」「ゅ」「ょ」はそれぞれ「つ」「や」「ゆ」「よ」の後に並べる。
  4. 小文字「ァ」「ィ」「ゥ」「ェ」「ォ」はそれぞれ「あ」「い」「う」「え」「お」の後に並べる。
  5. 長音符「ー」は直前の母音を表すものとし、それぞれ仮名の後に並べる。
    例: 「コーヒー」は「こおひい」と見なす。
  6. ひらがなとカタカナは、この順に並べる。
プロジェクト‐ノート:索引/配列順 - Wikipedia

成果物

PHP で Composer から使用できるライブラリを作成した。次のコマンドでプロジェクトに追加できる。

composer require sharapeco/kana-order

次のように KanaOrder::get メソッドを使用して既存のデータに並べ替え用のデータを付加し、 usort 関数を使って並べ替えを行うことができる。

<?php
require_once 'vendor/autoload.php';
use sharapeco\KanaOrder\KanaOrder;

$items = [
	['name' => '進め', 'kana' => 'すすめ'],
	['name' => 'スズメ', 'kana' => 'スズメ'],
	['name' => '鈴なり', 'kana' => 'すずなり'],
	['name' => 'ススキ', 'kana' => 'ススキ'],
	['name' => 'すり抜け', 'kana' => 'すりぬけ'],
];

$items = array_map(function($item) {
	$item['sort_key'] = KanaOrder::get($item['kana']);
	return $item;
}, $items);

usort($items , function($x, $y) {
	return strcmp($x['sort_key'], $y['sort_key']);
});

リポジトリおよび Packagist のページはこちら。

別の方法:ICU ライブラリを使用する

ICU は International Components for Unicode の略で、ロケール関連のさまざまな操作ができるライブラリだ。PHP からは intl 拡張モジュールとして使用できる。

PHP ではその中にある Collator クラスでロケール固有の並び順を考慮した文字列の比較、ソートができる。

単純に項目を並べ替える場合は sort メソッドが使用できる。

<?php
$items = ['すすめ', 'すずめ', 'すずなり', 'すすき', 'すりぬけ'];

$collator = new Collator('ja_JP');
$collator->sort($items);

また日本語では読みがな単体を並べ替える場合は少ないだろうから、 getSortKey メソッドを使い、次のように書ける。

<?php
$items = [
	['name' => '進め', 'kana' => 'すすめ'],
	['name' => 'スズメ', 'kana' => 'スズメ'],
	['name' => '鈴なり', 'kana' => 'すずなり'],
	['name' => 'ススキ', 'kana' => 'ススキ'],
	['name' => 'すり抜け', 'kana' => 'すりぬけ'],
];

$collator = new Collator('ja_JP');
$items = array_map(function($item) use ($collator) {
	$item['sort_key'] = $collator->getSortKey($item['kana']);
	return $item;
}, $items);

usort($items , function($x, $y) {
	return strcmp($x['sort_key'], $y['sort_key']);
});

ところがこのライブラリを使った方法では一般的に使われている規則と違い、次のような差異がある。

  • ひらがなよりカタカナが先に来る
  • 促音、拗音、小文字、長音符が通常のかなより先に来る

そのため新しくライブラリを作成した。

アイディア

ひらがなとカタカナに関するソートキーを得る方法として、各かなを「清音 + 付加情報」として表す方法を考えた。はじめに清音でソートし、清音が等しい場合は付加情報を付加した文字列を比較する。ラテン文字や算用数字、記号については考慮していない。

  1. ひらがな清音の場合は付加情報としてスペースを足す(すすめ→すすめ SP SP SP; 見えないので SP と表記)
  2. ひらがな濁音は " (すずめ→すすめ SP " SP)
  3. ひらがな半濁音および拗促音は $ (ぴゅっ→ひゆつ$$$)
  4. カタカナ清音は ! (ススメ→すすめ!!!)
  5. カタカナ濁音は # (スズメ→すすめ!#!)
  6. カタカナ半濁音および拗促音は % (ピュッ→ひゆつ%%%)

このとき SP ! " # $ % はコード順になっていることに留意。

Google Analytics API を PHP から使う

はじめに

この記事は、次の記事を現在(2016年4月)の Google Analytics およびその API の実情に合わせて書き直したものである。

人気記事ランキングを作成する処理等、ユーザが操作を介することのないバックエンドのプログラムが Google Analytics API にアクセスする方法について説明している。

1. API を利用可能にする

前準備として、Google Developer Console にアクセスして API を有効にする必要がある。

Google Developer Console を開き、プロジェクトを選択するか、無ければ作成する。

API の検索ボックスから「Analytics API」を検索し、名前をクリックすると API の詳細が表示されるので、その画面から有効にする。

2. API アカウントを作成し、キーを取得する

プロジェクトを作成・選択したら、「認証情報」をクリックして API にアクセスするための「サービス アカウント」を作成する。

認証情報が作成されていない場合は次のような画面になるので、「認証情報を作成」→「サービス アカウント キー」を選択する。

サービス アカウント キーの作成という画面になるので、「Service account name」にサービス名を入力する。サービス名を入力すると「サービス アカウント ID」は自動入力されるので、変更が必要なければそのままでよい。そして「キーのタイプ」は必ず「P12」を選択するGoogle APIs Client Library for PHP が P12 形式にしか対応していないためだ。

サービス アカウントを作成すると、自動的にキーのダウンロードが始まる。この *.p12 ファイルを保存しておく。
次にサービス アカウント ID を確認する。サービス アカウント ID はメールアドレス形式の ID で、「サービス アカウントの管理」から確認できる。

3. Analytics へのアクセス権限を API アカウントに付与する

前項で作成したサービス アカウントが Analytics のデータにアクセスできるよう、権限を付与する必要がある。

Google Analytics にアクセスし「アナリティクス設定」を開く。

アクセスを許可したいアカウントを選択し、「ユーザー管理」を開く。そこに「権限を付与するユーザー」という項目があるので前項で作成した「サービス アカウント ID」を入力する。

権限は「表示と分析」のままでよい。ここで「追加」を行うと、API から Analytics のデータを取得することができるようになる。

4. Google APIs Client Library for PHP を設定する

PHP から API を利用するために、Google APIs Client Library for PHP をインストールする。

PHP のパッケージマネージャ Composer を使用する場合は、ライブラリを灰位置するディレクトリに次の内容の composer.json を作成し、composer install コマンドを実行する。

{
    "require": {
        "google/apiclient": "1.1"
    }
}

5. API を使い Analytics データを取得する

準備が整ったら API からデータを取得してみよう。次のコードは過去 30 日分を対象に、サイト内(ビュー)でよくアクセスされているページのタイトル、URL、セッション数を取得するものである。

その他、取得できる主なデータは次項で説明している。

<?php
require_once 'my_google_analytics.php';

$ga = MyGoogleAnalytics::get();
$result = $ga->data_ga->get(
	'ga:XXXXX', // XXXXX の部分は Analytics のビュー ID
	'30daysAgo', // 開始日 ("2016-04-04" という指定もできる)
	'today', // 終了日
	'ga:sessions', // 主要指標 (metrics)
	[
		'dimensions' => 'ga:pageTitle,ga:pagePath', // 副指標
		'sort' => '-ga:sessions', // - を付けると降順ソート
		'max-results' => 10, // 取得件数
	]);

// 2次元配列として取得できるのでこれを加工する
print_r($result->getRows());

ここで読み込んでいる my_google_analytics.php の内容は次の通り。サービス アカウント ID と秘密鍵 (P12) ファイルを指定するだけですぐに使えるようになる。

<?php
// Composer により作成された verndor/autoload.php を読み込む
require_once 'path/to/vendor/autoload.php';

class MyGoogleAnalytics {

	// サービス アカウント ID
	const SERVICE_ACCOUNT_ID = 'XXXXXXXXXXXXXXXX@XXXXXXXXXXX.iam.gserviceaccount.com';

	// 秘密鍵のファイル名
	const KEY_FILE = 'path/to/XXXXXXXX.p12';
	
	private static $client;
	private static $service;

	public static function get() {
		if (!isset(self::$client)) {
			self::createClient();
		}
		if (!isset(self::$service)) {
			self::createService();
		}
		return self::$service;
	}

	private static function createClient() {
		$client = new Google_Client();

		// もしサーバの /var/tmp に書き込めないなどの事情がある場合は
		// ここでキャッシュファイルを生成するディレクトリを指定する
		//$client->setClassConfig('Google_Cache_File', ['directory' => __DIR__ . '/tmp']);

		// アプリ名。どこで使うのかは不明
		$client->setApplicationName('My Access Analysis');

		$cred = new Google_Auth_AssertionCredentials(
			self::SERVICE_ACCOUNT_ID,
			[Google_Service_Analytics::ANALYTICS_READONLY],
			file_get_contents(self::KEY_FILE)
		);
		$client->setAssertionCredentials($cred);
		if ($client->getAuth()->isAccessTokenExpired()) {
			$client->getAuth()->refreshTokenWithAssertion($cred);
		}
		
		self::$client = $client;
	}

	private static function createService() {
		self::$service = new Google_Service_Analytics(self::$client);
	}
}

6. 取得できるデータの種類

指標 metrics および dimension で指定できる項目はこのページで参照できる。

主な指標を挙げておく。

  • Metrics
    • ga:sessions セッション数
    • ga:users ユーザ数
    • ga:pageviews ページビュー
  • Dimensions
    • ga:userType 新規訪問/リピート訪問 (例: New Visitor, Returning Visitor)
    • ga:medium 参照元の種類 (例: organic, referral, (none))
    • ga:source 参照元 (例: google, yahoo, (direct), example.com)

(調査中) Windows 版 PHP の ZipArchive クラスで日本語 (CP932) のファイルを扱えない?

Windows 上で作成した、ファイル名が CP932 の Zip ファイルがどうしても文字化けしてしまう。たとえば “日本語” (93 fa 96 7b 8c ea) というファイルは常に “ô·û{îΩ” (c3 b4 c2 b7 c3 bb 7b c3 ae ce a9) という不可解な化け方をする。
試したこと:

  • set_locale() でロケールを Japanese_Japan.932 に設定する

PHP 7 を Windows 7 上で動かす

PHP 7.0.1 がリリースされたので、ひとまず開発環境である Windows 7 上で動かしてみた。

インストール

PHP のダウンロードページから「Windows downloads」のリンクをたどり、Windows 版のダウンロードページへ。

ここでは x64 スレッドセーフ版を選び、 C:/Apps/php/php-7.0.1-Win32-VC14-x64 に展開した。

また、PHP 7 を動かすためには Visual C++ (2015) のランタイムが必要なので、導入していない場合はインストールする。

PHP は x64 版を選んだのでこちらも vc_redist.x64.exe を選ぶ。

php.ini の設定

展開したディレクトリの php.ini-development を php.ini という名前でコピーし、次の設定を行った。
703行目、現在使用しているライブラリを使用できるようにする。(バージョンの違いで動かない可能性もある)

include_path=".;C:\xampp\php\PEAR;C:\xampp\php\share"

725行目、拡張 DLL の読み込み設定。

extension_dir = "ext"

867行目〜、必要な拡張機能を有効にするため、コメントを外す。

extension=php_mbstring.dll 等

パスを通すためのバッチファイル作成

PHP 7 を一時的に使用するため、システムへのパスの設定は行わない。そのかわり次のようなバッチファイルを作成し、必要なときにパスを通すようにする。

PATH C:\Windows\system32;C:\Windows;C:\Apps\php\php-7.0.1-Win32-VC14-x64

ここでは usephp7.bat という名前でパスの通ったディレクトリに保存した。

Web アプリケーションを実行する

PHP には 5.4.0 から開発用のビルトインウェブサーバー機能がある。この機能を利用することによって、あるディレクトリをドキュメント領域とした Web サーバを起動することができる。

サーバの起動は簡単。さきほど作ったバッチファイルを使い、コマンドプロンプトから

> cd public_html
> usephp7
> php -S localhost:8000

のようにする。
この状態で Web ブラウザから http://localhost:8000 にアクセスすると、 public_html 内のファイルを表示、PHP なら実行できる。
Web サーバを停止するにはコマンドプロンプト上で Ctrl-C を押せばよい。

感想

試しにある程度テキストを処理するプログラムを動かしてみたが、PHP 5.6 では 750 ms ほどかかっていた処理が 400 ms 前後で終わるようになった。1.8〜1.9 倍も速い計算になる。

mPDF (5.7.4) はタイムゾーン設定を勝手に書き換えるので注意

HTML で書かれたコードから PDF を出力できる mPDF という PHP ライブラリがある。HTML と CSS を理解していれば思い通りに、とまではいかないまでも、それなりにレイアウトすることができる。

あるときこの mPDF を使っていたところ、出力される日時がずれてしまう問題が発生した。 date_default_timezone_set() を使ってタイムゾーンを設定しているのにおかしいと思い、コードを追いかけると mPDF を読み込む段階でタイムゾーン設定が書き換わっていることが分かった。

<?php
date_default_timezone_set('Asia/Tokyo');
echo date_default_timezone_get(); // => Asia/Tokyo

require_once '/path/to/mpdf.php';

echo date_default_timezone_get(); // => Europe/London

原因は mpdf.php の 54 行目に書かれている次の一文。

if (ini_get("date.timezone")=="") { date_default_timezone_set("Europe/London"); }

このコードではスクリプトタイムゾーンが設定されていても、ini ファイルにタイムゾーンの設定がなければ date_default_timezone_set("Europe/London") が実行されてしまう。もしタイムゾーンの設定がどこにもなければ PHP はデフォルトのタイムゾーンである UTC を使用するので、わざわざタイムゾーンを設定する必要はない。よって mpdf.php の 53〜55 行目をコメントアウトして使用する。

mPDF の GitHub プロジェクトでもこの問題がバグとして報告されている。