PHP

kusano

初投稿となります、クライゼルグループの kusano です。

何を書こうか考えたのですが、PHP の文字列チェック関数について少し気になったことがあったので、この機会に簡単な調査をしてみました。

PHP で文字列がどういった文字で構成されているかを確認する関数として有名なのは preg_match でしょうか。正規表現によるマッチングを行えるので万能ですね。

一方、文字列が「0 から 9 の数字」のみで構成されているかどうかのみをチェックする代わりに速い (らしい) 関数として ctype_digit といったものもあると聞きました。

以下では、ctype_digit 関数とはそもそもどういった関数かをまとめた上で、preg_match との速度比較を行ってみようと思います。

ctype_digit とは

ctype_digit 関数の仕様については こちら に書いてある通りで、「与えられた文字列 text のすべての文字が数字であるかどうかを調べます。」となります。

ただ、リンク先ページの「注意」にも書いてあるとおり、気をつけるべき点があります。以下に引用します。

  • 注意:
    • この関数を活用するには string を渡さなければなりません。 たとえば integer を渡すと、期待する結果にならない可能性があります。 HTML フォームに入力された整数値は、integer ではなく string 型で返されます。 マニュアルの 型 についての節を参照ください。
    • -128 から 255 までの整数値を渡すと、ひとつの文字の ASCII 値とみなします (負の値には 256 を足して、拡張 ASCII の範囲に収まるようにします)。 それ以外の整数値は、10 進数を含む文字列とみなします。

つまり、-128 から 255 までの整数値を ctype_digit に渡してしまうと、文字列が 0 から 9 の数字のみで構成されているかどうかではなく、その ASCII 値に対応する文字列が 0 から 9 の数字かどうか、がチェックされるというわけですね。

実際に簡単なプログラムを動かして ctype_digit の振る舞いを見てみます。

<?php

for ($i = -512; $i <= 512; $i++) {
    if ($i < 256 and $i > -129) {
        echo "ASCII Code: ";
        echo chr($i);
    } else {
        echo "Non ASCII Code";
    }
    
    echo " ($i): ";
    var_dump ( ctype_digit($i) );
}

上記のコードを実行すると、以下のような結果が出ます。 (長いので一部割愛します)


Non ASCII Code (-512): bool(false)   ← ASCII 値とは扱わないが 
Non ASCII Code (-511): bool(false)      "-" が含まれるので false
             :
Non ASCII Code (-129): bool(false)
ASCII Code: <80> (-128): bool(false) ← ASCII 値として扱い、
ASCII Code: <81> (-127): bool(false)    0-9 の文字ではないので false
             :
ASCII Code:  (-2): bool(false)
ASCII Code:  (-1): bool(false)
ASCII Code: ^@ (0): bool(false)
             :
ASCII Code:   (32): bool(false)
ASCII Code: ! (33): bool(false)
             :
ASCII Code: . (46): bool(false)
ASCII Code: / (47): bool(false)
ASCII Code: 0 (48): bool(true)  ← ASCII 値として扱い、
             :                     0-9 の文字なので true
ASCII Code: 1 (49): bool(true)
ASCII Code: 9 (57): bool(true)
ASCII Code: : (58): bool(false) ← ASCII 値として扱い、
ASCII Code: ; (59): bool(false)    0-9 の文字ではないので false
ASCII Code: < (60): bool(false)
             :
ASCII Code:  (254): bool(false)
ASCII Code:  (255): bool(false)
Non ASCII Code (256): bool(true)  ← ASCII 値と扱わず、0-9 の文字だけで
Non ASCII Code (257): bool(true)     構成されると見なされ true
             :

実際にこういったプログラムを書いてみると、どういった動きになるかひと目で分かりますね。

なお、float 型や bool 型はどの値を与えても false が返ります。

したがって、ctype_digit を利用する場合、あえて「ASCII 値に対応する文字列が 0 から 9 の数字かどうかをチェック」するのでない限り、値を必ず string 型にしてから渡すようにするよう注意が必要ということですね。

ちなみに、preg_match に string 型以外を渡すと、string 型にキャストして判定されるようです。

ctype_digit と preg_match との速度比較

ctype_digit と preg_match との速度比較 としては、ctype digit と preg matchでの文字列比較速度 で比較検証がされていますね。こちらの結果によるとほとんど両者の速度は変わらないそうです。

ただ、こちらで紹介している方法ですと、それぞれの関数による処理時間に加え、以下の処理時間も計測に含まれているようです。

  • 文字列の生成処理
  • echo による出力処理

仮に上の 2 つの処理が ctype_digit と preg_match に比べて数倍以上時間がかかってるとすると、2つの関数の処理時間の差が埋もれてしまっているかもしれませんね。

そんなわけで、ctype digit と preg matchでの文字列比較速度 で紹介されているコードから、echo による出力を省き、文字列生成と文字列チェックのそれぞれの処理の前後に microtime 関数を入れて、それぞれにかかる時間を計測できるようにしたコードで改めて計測を行ってみました。

検証に使用したコード

【string_generator2.php】

<?php

function generate_random_strings($type='letters')
{
    switch($type){
      case 'digits':
        $asset = '0123456789';
        break;
      case 'letters':
      default:
        $asset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        break;
    }

    $length = strlen($asset);
    $max = rand(4, 32);
    $result = array();
    for($i = 0; $i < $max; $i++){
        array_push($result, $asset[rand(0, $length-1)]); 
    }

    return implode($result);
}

function generate_list($max=100, $type='letters')
{
  $start_time = microtime(true);
  $result = array();
  for($i = 0; $i < $max; $i++){
    array_push($result, generate_random_strings($type));
  }
  echo "str_gen:\t";
  echo microtime(true) - $start_time . " sec\n";
  return $result;
}

function generate_letters_list($max=100)
{
  return generate_list($max);
}

function generate_digits_list($max=100)
{
  return generate_list($max, 'digits');
}

【ctype_digit_bench.php】

<?php

require_once('string_generator2.php');

function validate($variables)
{
  $start_time = microtime(true);
  foreach($variables as $v){
    if(ctype_digit($v)){
      $valid++;
    }
    else{
      $invalid++;
    }
  }

  echo "ctype_digit:\t";
  echo microtime(true) - $start_time . " sec\n";

  echo "# valid:   $valid\n";
  echo "# invalid: $invalid\n";
}

validate(generate_letters_list(100000));
validate(generate_digits_list(100000));

【preg_match_bench.php】

<?php

require_once('string_generator2.php');

function validate($variables)
{
  $start_time = microtime(true);
  foreach($variables as $v){
    if(preg_match('/^[0-9]+$/', $v)){
      $valid++;
    }
    else{
      $invalid++;
    }
  }

  echo "preg_match:\t";
  echo microtime(true) - $start_time . " sec\n";

  echo "# valid:   $valid\n";
  echo "# invalid: $invalid\n";
}

validate(generate_letters_list(100000));
validate(generate_digits_list(100000));

上記のコードによる計測内容は要約すると以下のようになります。こちらの 1,2 をそれぞれ時間計測しています。

  1. 10万 x 2 行のランダム文字列を生成(generate_letters_list および generate_digits_list 関数)し、validate 関数に渡す
  2. validate 関数内で、それぞれ ctype_digit と preg_match で文字列の検査を実施

検証した環境

上記のコードを、以下の環境で 10 回実施し、その平均値をとってみました。

  • OS : CentOS 6.2
  • CPU : Intel Core2Duo 3.33GHz
  • Memory : 512MB
  • PHP : Ver.5.3.3

検証結果

  • ctype_digit_bench.php
    • A-Z, a-z, 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.28 秒
      • ctype_digit: 0.175 秒
    • 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.29 秒
      • ctype_digit: 0.170 秒
  • preg_match_bench.php
    • A-Z, a-z, 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.32 秒
      • preg_match: 0.274 秒
    • 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.46 秒
      • preg_match: 0.289 秒

この結果から、以下のように考えられるのではないかと思われます。

  • 文字列生成は文字列チェック (ctype_digit と preg_match) に比べ、10 倍以上時間がかかる
  • ctype_digit の方が preg_match より 1.5 倍程度速い

参考にさせていただいたサイトでの検証ですと、前者の点で ctype_digit と preg_match の差が文字列生成側の誤差の範囲内になってしまっていたのかもしれませんね。

ctype_digit の方が単機能なので当たり前といえば当たり前ですが、一応これで ctype_digit と preg_match との正味のチェックの処理時間の差が確認できたかと思います。

まとめ

計測の結果、ctype_digit は preg_match の 1.5 倍程度速いようでした。ただ、preg_match のように int 型で渡した場合に string 型と同じようには判定してくれませんので、利用する際は注意が必要になりますね。

以上、今更ネタかもしれませんが、何かのお役に立てれば幸いです。

Tags: , ,

kid

はじめまして、セールスフォースグループの kid です。

厳密には 2 度目の記事ですが、以前記事を書いた際にはクライゼルチームでしたし、時間もかなり空いていましたので、簡単に自己紹介させていただきます。

私の所属するセールスフォースグループの業務ではクラウド上のCRMサービスとなる「セールスフォース」と連携したメール配信アプリケーション「Autobahn for Appexchange」の開発、運用しています。サービス基盤となるセールスフォースとデータ連携を取りながら、セールスフォース単独では難しいような大量のメール配信を実現しています。

[セールスフォースのメール配信機能を拡張「Autobahn for AppExchange」]
https://appexchangejp.salesforce.com/listingDetail?listingId=a0N300000016cKNEAY

はじめに

さて、まずセールスフォースをご利用されたことがない方も多いかと思いますので、APIを含む開発に至る概要を説明させていただきます。セールスフォースではお客様のデータや契約情報、商談状況などを管理・レポートする充実した SFA (Sales Force Automation) の機能を実装しています。最近では、マーケティングやコールセンターのための機能を実装するなど多種多様にわたったサービスとなっており、多くの企業にて導入されています。

セールスフォースの特徴の一つとして、利用者の環境に合わせて簡単にカスタマイズ可能な点が挙げられます。カスタマイズは HTML や CSS、JavaScript といったコード知識がなくとも管理画面上からマウス操作のみで行えます。ボタンを配置したり、条件ロジックを作成して承認機能を付けたりなど、プログラムを一切書くことなく開発を実施できるプラットフォームを実現しています。

一方で、セールスフォースは API も提供しており、外部サービスとの連携が容易という点も大きな特徴です。私達セールスフォースグループが提供する Autobahn for Appexchange もそういった外部連携サービスの一つで、こちらを利用することで高性能なメール配信機能を備えたセールスフォースを利用可能になります。

この他にも既存のシステムから完全移行が困難だったり、セキュリティポリシー面で個人情報となるデータをクラウド上におくことができないといった場合であっても、外部サービスとの連携が容易であるため、既存システムとセールスフォースを組み合わせて利用するといったことも可能です。実際にそのような形で利用されているお客さんも多くいます。

私からはセールスフォースにあるデータとの連携をテーマに、業務の中で培ったノウハウをご紹介していければと思います。

まず今回は、自分たちのサーバからセールスフォース上のデータに対して、PHP を利用してどのようにデータの参照・操作を実現していくのかをご紹介します。

今回の実例では以下の基本的な知識は持っている前提とします。

  • PHP
  • SOQL – セールスフォース上で利用可能な SQL 文となります。

それではまずセールスフォース上にあるデータ参照するための方法を手順を追って実装していきましょう。初回ですので、検証環境の用意から説明させていただきます。

1. API を利用できるセールスフォースの Edition について

外部からのAPIを利用する場合、セールスフォースにて API オプションが利用可能である必要があります。セールスフォースは Edition といったサービスランクで利用できる機能が区分されており、API を利用する際は Enterprise Edtion と同等以上の Edition である必要がありますのでご注意ください (参考: API アクセスのあるエディション – Salesforce.com Help Portal)。

また、API にはコール数の制限もありますので、実運用されている環境で直接実装するのではなく、検証用の Sandbox や開発者向けの Developer Edition にてご検証いただくようお願いします (参考: API の使用制限 – Help – Salesforce.com)。

以降の API によるデータの参照・操作は Developer Edition で行ってみたいと思います。

2. 検証環境を用意します

2.1. セールスフォースの組織を用意します

セールスフォースでは本番環境も期間限定の無料トライアルで利用することができますが、利用期間が限られているため、開発者用に用意された Developer Edtion を利用していきましょう。以下のサイトより無償で取得できます。

[developerforce]
http://jp.force.com/

2.2. PHPを実装できる環境を用意します

今回はPHPを利用してAPIの実装を検証しますが、PHPが動作する環境を用意するものは結構手間なものになりますので、今回はクラウド上の IDE (Integrated Development Environment) ツール Cloud9 IDE といったサービスを利用していきます。Standard 版であれば無償で利用可能ですので、画面上からサインアップしてアカウントを取得してみてください。

[Clout9 IDE]
https://c9.io

2.3. アカウントを取得したらログイン、PHPの開発環境を作成します

画面左上に、「CREATE NEW WORKSPACE」とありますので、クリックして新規作成できます。

3.2.menu

2.4. PHPを選択して、開発環境を作成します

他のプログラム言語での利用できますので、ご興味のある方は後ほど試してみてください。

5.4.PHP_Project

開発はブラウザ上から行うことができ、コンソールでの動作確認もできます。

sf-api-cloud9-ui01

 

2.5. APIを実装するためのライブラリファイルを用意します

APIの実装をゼロから実現するのはかなりハードルが高いため、一般的なサービスでは API をより活用してもらえるようにプログラム言語に合わせてライブラリを公開していることがよくあります。セールスフォースでも同様に PHP 用の Toolkit が提供されていますのでそちらを利用します。API はいくつかありますが今回は Soap API を用います。合わせて開発マニュアルについてもご紹介しておきます。

5.1.php_toolkit

[Force.com Toolkit for PHP]
http://wiki.developerforce.com/page/Force.com_Toolkit_for_PHP

[開発ドキュメント]
http://wiki.developerforce.com/page/JP:Documentation

Download の PHP toolkit をダウンロードすると GitHub の画面に移動しますので、右下の [Download ZIP] から Zip ファイルをダウンロードしましょう。ダウンロードし、手元で展開すると Force.com-Toolkit-for-PHP-master というフォルダが作成されるはずです。

続いて Cloud9 IDE 画面の左上 (WORKSPACE FILES) 部分で右クリックし、[New Folder] から soapclient フォルダを作成します。作成後、そのフォルダ上で右クリックして [Upload to this folder] を選択するとドラッグ&ドロップ用のウィンドウが表示されます。ここに、先ほど展開した Force.com-Toolkit-for-PHP-master 内の soapclient フォルダ内のファイル全てをドラッグ&ドロップしましょう。

最終的に、以下のような配置となります。以降のプログラムはこの配置前提で実行していくこととなります。

5.5.command_run

2.6. WSDL ファイルをダウンロードし、Cloud9 IDE へアップロードします

Salesfoce に API でアクセスするため、WSDL (Web Service Description Language) ファイルをセールスフォースの管理画面からダウンロードし、Cloud9 IDE にアップロードします。

Salesfoce にログインし、画面の右上の [設定] → 左下の [開発] → そのサブメニューの [API] → 右ペインに出てくる [パートナー WSDL の生成] を右クリックしてファイルを保存します。ファイル名は partner.wsdl.xml としておきましょう。

続いて Cloud9 IDE で configs というフォルダを作成し、その中に partner.wsdl.xml をアップロードします。やり方はライブラリをアップロードした方法と同様です。最終的に以下のような構成となります。

sf-api-libs-structure01.png

2.7. セキュリティトークンを発行します

準備の最後に、セールスフォースの管理画面からセキュリティトークンの発行を行います。手順は以下の通りです。

Salesfoce にログイン → 画面の右上の名前の横にある下向き矢印をクリック → 名前の下にあるメニューで [私の設定] を選択 → 画面左側の [個人用] をクリック → サブメニューの [私のセキュリティトークンのリセット] を選択

これでセールスフォースに登録されたメールアドレスにセキュリティトークンが送信されます。

3. プログラムを書きます

ではプログラムを書いていきましょう。PHP のバージョンは 5.3.3 を前提にしています。

3.1. 必要なライブラリを読込みます

// Toolkitを読込みます。
require_once("./soapclient/SforcePartnerClient.php");

3.2. 設定ファイルを用意します

// 事前に必要な情報を宣言します。 

// 今回は特定のセールスフォース組織に依存しないような場合に利用する
// Partner WSDL ファイルを利用します。
define("PARTNER_WSDL_FILE", "./configs/partner.wsdl.xml"); 

// セールスフォースへAPI接続する場合、接続元のIPアドレス許可が必要となりますが、
// 代替手段として、今回はセキュリティトークンを発行してIPアドレス許可の設定はスキップします。
define("SECURITY_TOKEN", "*************************"); 

// API でログインするセールスフォースのアカウントです。
define("LOGIN_ID", "salesforce_api@example.com");

// パスワードの後ろにセキュリティトークンを付けます。
define("LOGIN_PASS", "*********" . SECURITY_TOKEN); 

3.3. 実際にログイン処理を書いていきます

// 接続用クラスを生成します。
$sforce_connection = new SforcePartnerClient();
$soap_client = $sforce_connection->createConnection(PARTNER_WSDL_FILE, null);

try {
	// セールスフォースへログインを実行します。
    $login = $sforce_connection->login(LOGIN_ID, LOGIN_PASS);
    var_dump($login);
} catch (Exception $e) {
    var_dump($e);
}

4. 実際に動作させてみます

4.1. 実行結果を確認してみます

$ php api.php 
object(stdClass)#4 (7) {
  ["metadataServerUrl"]=>
  string(62) "https://ap.salesforce.com/services/Soap/m/29.0/00D10000000ZSLk"
  ["passwordExpired"]=>
  bool(false)
  ["sandbox"]=>
  bool(false)
  ["serverUrl"]=>
  string(62) "https://ap.salesforce.com/services/Soap/u/29.0/00D10000000ZSLk"
  ["sessionId"]=>
  string(112) "00D10000000ZSLk!AR4AQMzhSrUA8V.CInjnDYM7tJlXqPW9FjKB96MBbL8YmXlCfCTk2O.HROS68IGBFRtvb1aodrYsVYhzPHQsYxfbaCzz.Pi0"
  ["userId"]=>
  string(18) "0051000000281V1AAI"
  ["userInfo"]=>
  object(stdClass)#5 (22) {
    ["accessibilityMode"]=>
    bool(false)
    ["currencySymbol"]=>
    string(3) "¥"
    ["orgAttachmentFileSizeLimit"]=>
    int(5242880)
    ["orgDefaultCurrencyIsoCode"]=>
    string(3) "JPY"
    ["orgDisallowHtmlAttachments"]=>
    bool(false)
    ["orgHasPersonAccounts"]=>
    bool(false)
    ...
    ...
  }
}

上記のようなフォーマットで値が返ってきます。
結果は QueryResult といったクラスに引き渡してもいいですし、必要な値だけ取得するようにパースするかは開発者によりまちまちですので、ここでは割愛します。
参考までに Session Id だけを取得してみましょう。

try {
    $login = $sforce_connection->login(LOGIN_ID, LOGIN_PASS);
    
    $login_info = get_object_vars($login);
    $session_id = $login_info['sessionId'];
    echo "Session Id : " . $session_id . "\n";
} catch (Exception $e) {
    var_dump($e);
}
$ php api.php 
Session Id : 00D10000000ZSLk!AR4AQMzhSrUA8V.CInjnDYM7tJlXqPW9FjKB96MBbL8YmXlCfCTk2O.HROS68IGBFRtvb1aodrYsVYhzPHQsYxfbaCzz.Pi0

4.2. データを参照します

セールスフォース上にあるデータに SOQL を発行してデータを取得します。

// 通常の SQL と大きく変わりませんが、少し独特な個所がありますので徐々に慣れていきましょう。
try {
    $query = "SELECT OrganizationType FROM Organization ";
    $result = $sforce_connection->query($query);
    $parse_result = get_object_vars($result);
    var_dump($parse_result);

} catch (Exception $e) {
    var_dump($e);
}
$ php api.php 
array(5) {
  ["queryLocator"]=>
  NULL
  ["done"]=>
  bool(true)
  ["records"]=>
  array(1) {
    [0]=>
    object(stdClass)#8 (3) {
      ["type"]=>
      string(12) "Organization"
      ["Id"]=>
      NULL
      ["any"]=>
      string(60) "Developer Edition"
    }
  }
  ["size"]=>
  int(1)
  ["pointer"]=>
  int(0)
}

4.3. データを作成してみます

セールスフォースにリードとなるデータの 1 つ追加してみます。

// 追加するデータは stdClass を利用して作成します。
$insert_data = new stdClass();
$insert_data->type = "Lead";
$insert_data->fields = array(
    "Company"  => "Tricorn", 
    "LastName" => "Koido",   
    );
try {
    $result = $sforce_connection->create(array($insert_data));
    var_dump($result);
	
} catch (Exception $e) {
    var_dump($e);
}
$ php api.php 
array(1) {
  [0]=>
  object(stdClass)#11 (2) {
    ["id"]=>
    string(18) "00Q1000000CIMDiEAP" // 作成されたレコードのId値が返ってきます。
    ["success"]=>
    bool(true)
  }
}

追加したデータはセールスフォースの画面でも確認できます。上記のプログラムでデータを追加した場合は、[リード] を選択して [私の未読リード] を表示し、[作成日] でソートすると一番新しいものとして表示されます。

sf-api-add-new-lead01.png

4.4. データを削除してみます

作成したデータを削除してみます。

// 削除した Id を配列で渡すことで削除されます。
try {
    $result = $sforce_connection->delete(array("00Q1000000CIMDiEAP"));
    var_dump($result);

} catch (Exception $e) {
    var_dump($e);
}
$ php api.php 
array(1) {
  [0]=>
  object(stdClass)#10 (2) {
    ["id"]=>
    string(18) "00Q1000000CIMDiEAP" // 正常に削除されたレコードIdが返ってきます。
    ["success"]=>
    bool(true)
  }
}

今回はここまで

いかがでしたでしょうか。初回ということであまり細かいことを気にせず、まずは API を利用してデータを操作する工程を紹介しました。次回以降も今回の内容をもとに、PHP をベースにしたセールスフォース上での開発ノウハウをご紹介していければと思います。

なお、次回は今回紹介した API を実装する上で回避することのできない API の制限や、ガバナ制限についても触れていきたいと思います。

最後に

本記事の内容は 2013年11月21日現在に検証した内容をもとに記載しています。セールスフォースのバージョンアップやライブラリの仕様変更などに伴い、動作をしなくなるといった可能性がありますので、詳細な点は公式マニュアルと合わせてご参照いただければ幸いです。

Tags: , , , ,

yamada

PHPでDKIM署名を作成する

Posted Date: 2013-10-25 13:00
Author: / No Comments

はじめまして。入社 2 年目クライゼルグループの yamada です。
初投稿となります。

最近、電子メールの認証技術として主流になりつつあるDKIM署名ですが、ネットで調べてもDKIM署名を作成するプログラムについてはこれといった情報がなかったので、今回はライブラリを使って DKIM 署名 (DKIM-Signatureヘッダ) を作成するプログラムを紹介します。

ライブラリのダウンロード

ネットで検索すると PHP の DKIM ライブラリはいくつかヒットしますが、今回は下記URLのライブラリを使います。

zipに含まれる「dkim.class.php」がライブラリの本体です。それ以外はテスト用のサンプルソースなので今回は使いません。

DKIM-Signatureヘッダの仕様決め

次にDKIM-Signatureヘッダの仕様を決めます。今回は下記の通りとします。

  • a タグ (署名作成のアルゴリズム) : rsa-sha1
  • q タグ (公開鍵取得方法) : dns/txt
  • c タグ (メールの正規化方式) : relaxed/simple (ヘッダ/本文)
  • h タグ (署名対象のヘッダ) : From、To、Subject
  • i、l、x、zタグは省略

※c タグのヘッダ正規化方式、a タグ、q タグについては、このライブラリでは固定値となります (変更する場合はライブラリの一部を書き換える必要があります)。

また、以下の値についても事前に決めておく必要があります。ここに設定する値はサンプルですので適宜変更して下さい。

  • d タグ (ドメイン) : example.com
  • s タグ (セレクタ) : dkim.sl.20131025

これらを踏まえて、作成する DKIM-Signature ヘッダは下記となります。t、bh、b タグについてはライブラリによって自動生成されることになります。

DKIM-Signature: v=1;
        a=rsa-sha1;
        q=dns/txt;
        s=dkim.sl.20131025;
        t=<送信日時>;
        c=relaxed/simple;
        h=from:to:subject;
        d=example.com;
        bh=<本文のハッシュ値>;
        b=<電子署名>

鍵の生成と DNS サーバへのレコード登録

DKIM 署名を行う秘密鍵と公開鍵のセットを作成します。CentOS 6 系であれば、opendkim パッケージの opendkim-genkey コマンドで簡単に作成できます。

# yum install opendkim
$ opendkim-genkey -d example.com -s dkim.sl.20131025

-d オプションにドメイン、-s オプションにセレクタを指定しています。上記コマンドを実行すると、以下のファイルが生成されます。

  • dkim.sl.20131025.private : 秘密鍵 (パスフレーズなし)
  • -----BEGIN RSA PRIVATE KEY-----
    MIICXAIBAAKBgQCo58N8KhMa1kblbGlhGINznh7kmQmK85kt2XSiaBmincAPeeLK
                          (省略)
    h0rlopDKO9J91moEQpkCQBeJDjKIzj3N1m2qqs4G6pWbMabHOE4StHJGAuIEFB4X
    ACOnSiiod6TaFl4QE6VI/6xH7s7gc/osRkoaFexIhcc=
    -----END RSA PRIVATE KEY-----
  • dkim.sl.20131025.txt : 公開鍵 (DNS レコード形式)
  • ; ----- DKIM key dkim.sl.20131025 for example.com
    dkim.sl.20131025._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfM (省略) DAQAB" 

この公開鍵 (DNS レコード形式) をドメインを管理する DNS サーバのレコードに事前に登録しておきます。ただし、未登録でも DKIM 署名のメール自体は送れるので、とりあえずライブラリの使い方を知りたいという方はスキップで OK です (もちろんその場合、署名されたメールは意図したとおりには検証されません)。

プログラム作成と実行

これで準備ができたのでプログラムを作っていきます。

なお、OpenSSL関数とマルチバイト文字列関数を使うため、事前にライブラリのインストールが必要です。CentOS 6 であれば以下で OK です。

# yum install openssl php-mbstring

プログラムは以下の通りです。例として宛先を to@example.com、送信者を from@example.com としていますので、ご利用の際は適宜書き換えて下さい。なお、以下のプログラムは PHP 5.3 で動作確認しています。

[send-dkim-mail.php]

<?php

include_once("dkim.class.php");

$to          = "to@example.com";
$subject     = "DKIM test";
$body        = "This is DKIM test mail.";
$headers     = "From: from@example.com";

$domain      = "example.com";
$selector    = "dkim.sl.20131025";

$private_key = "-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCo58N8KhMa1kblbGlhGINznh7kmQmK85kt2XSiaBmincAPeeLK
                      (省略)
h0rlopDKO9J91moEQpkCQBeJDjKIzj3N1m2qqs4G6pWbMabHOE4StHJGAuIEFB4X
ACOnSiiod6TaFl4QE6VI/6xH7s7gc/osRkoaFexIhcc=
-----END RSA PRIVATE KEY-----";

$pass_phrase = "";      // パスフレーズなし

$options = array(
    "dkim_body_canonicalization" => "simple",   // 本文の正規化方式
    "signature_headers" => array(               // 署名対象のヘッダ
        "From",
        "To",
        "Subject"
    )
);

$signature = new mail_signature(
    $private_key,
    $pass_phrase,
    $domain,
    $selector,
    $options
);

// DKIM-Signature ヘッダを作成する
$signed_headers = $signature->get_signed_headers($to, $subject, $body, $headers);

// 通常のメールヘッダの前に、作成した DKIM-Signature ヘッダを付け加える
$headers = $signed_headers . $headers;

// DKIM 署名 (DKIM-Signatureヘッダ) 付きのメールを送信する
mb_send_mail($to, $subject, $body, $headers);

このプログラムを以下のように実行すれば DKIM 署名付きのメールを送信することができます。

$ php send-dkim-mail.php

署名が正しく作成されたかどうかを確認するには、メールを Gmail などの DKIM に対応した Web メールに送るのが手っ取り早いでしょう。

Gmail 側で受信したメールのソースを開いて、Authentication-Results ヘッダに「dkim=pass」と記録されていれば、署名が正しく作成されたことになります。

今回紹介したライブラリは簡単で使いやすいですが、パラメータが固定されていたりと不便な点もあるので、ぜひその辺を改修したバージョンがリリースされることを期待したいですね。

Tags: ,

toda

システムの規模の肥大化に伴うクラス数増大により、あるクラスにメソッド追加が集中することがあります。基盤となるクラスのメソッドが増大していくものです。
クラスを追加するときに、その関連メソッドを基盤クラス側のメソッドとして追加していくとそのような事態に陥りがちです。いわゆる、Blob(肥満児)アンチパターンですね。

あまり上手くない例えですが、ある会員情報を保存している MemberTable クラスに対して、その会員情報を操作するフォームを表す Form クラスが追加されていることがある (1:N) とします。

その MemberTable に所属している Form の一覧を取得する getForms() メソッドを MemberTable クラスに追加したいわけですが、静的にメソッドを追加した場合、Form クラスが使われない場合は無駄なメソッドが追加されることになります。

Form クラスだけならまだしも、このようなクラスが増えていくと、常に使用されるとは限らないメソッドが MemberTable クラスに追加されていくことになり、MemberTableのコードが無駄に肥大化することになります。また、 Form クラスの修正の影響が MemberTable クラスに及ぶことがありえるため、修正すべき箇所が複数のコードに分散してしまい保守性の観点からも望ましくはありません。

この問題の一つの解決手段として、メソッドを(必要なときに)動的に追加することで、静的に定義されるメソッド数を減らすという方法があります。
Form クラスが読み込まれた時に、 Form クラスのコード側で MemberTable クラスに getForms() メソッドを追加することができれば、MemberTable クラスのソースに手を加える必要はないので、MemberTable の肥大化を防ぐことができますし、 Form クラスの修正を Form クラスのコードに抑えることができます。

具体的な実装例としては、動的に追加するメソッドを無名関数として登録しておき、基盤クラスの __call() マジックメソッドを経由して呼び出すという方法があります。

class MemberTable {
  // 動的に追加されたメソッドの配列
  static private $_methods = array();

  // 動的にメソッドを追加
  static public function addMethod($name, $func) {
    self::$_methods[$name] = $func;
  }

  // 動的に追加されたメソッドの呼び出し
  public function __call($name, $args)
  {
    $func = self::$_methods[$name];

    // 無名関数に $this を bind してから実行
    call_user_func_array($func->bindTo($this, 'MemberTable'), $args);
  }
}

Form クラス側のコードでは、読み込み時に MemberTable::addMethod() でメソッドを追加します。

class Form {
  // 詳細略
}

// MemberTable クラスに getForms() メソッドを追加
MemberTable::addMethod("getForms", function(){
  // 詳細略
  return $forms;
});

Form クラスが読み込まれるときに、MemberTable::addMethod() でメソッドを追加されているので、何も気にせずに getForms() を呼び出すことができます。

$forms = (new MemberTable())->getForms();

ポイントはメソッドの呼び出し時に、bindTo() で無名関数の $this を bind しておくことです。これを忘れると、無名関数内で $this を参照できません。
あと、bindTo() するときにスコープ(第2引数)にクラス名を指定しておく必要があることをお忘れなく。

しかし、bindTo() が使えるのは PHP 5.4 以降であって、公式パッケージが未だに PHP 5.3 である RedHat/CentOS では、無名関数を $this に bind することはできません。

そこで無名関数の呼び出し時に、引数に $this を暗黙的に追加して、無名関数側の引数として受け取るという方法があります。

class MemberTable {
  public function __call($name, $args)
  {
    // 引数の先頭に $this を追加
    array_unshift($args, $this);

    return call_user_func_array(self::$this->_methods[$name], $args);
  }
}

無名関数を登録するときは、最初の引数で $this を受け取るようにします。
クラスのメソッドは $self 経由で呼び出します。

class::addMethod('method', funciton($self, $arg1, $arg2..){
  // 詳細略
  return $forms;

  // メソッドを呼び出すときは $self から呼び出す
  $self->someMethod();
});

これである程度同様のことが実現できます。
もちろん制約もあって、$self は自身の private, protected メソッドを呼び出すことはできません。

早く RedHat/CentOS の公式パッケージが PHP 5.4 ベースになってくれることを期待したいところです。

Tags:

s-24

もうすっかり暑くなりましたが、1日中半袖でいることができない男、s-24です。(改名しました!)
ずっと見てきた海外ドラマ、CSI-NYがシーズン9で終了してしまい残念すぎます。
マイアミも終わってしまったので、とってもさみしいです。。
そのかわり、24が復活するらしいですが、今更ジャックについていけるか心配で寝れません!

さて、今回は PHPMD – PHP Mess Detector についてです。PHPMD は PHP のコードの中から、コードレベルでバグになりそうな箇所を検出してくれるツールです。早めにバグを発見するために導入してみました。

インストールは簡単です。CentOS であれば root 権限で以下を実行していけば OK!

# yum -y install php-pear
 
# pear channel-discover pear.pdepend.org
# pear install pdepend/PHP_Depend-beta
 
# pear channel-discover pear.phpmd.org
# pear install --alldeps phpmd/PHP_PMD-alpha

これでインストールできます。

実行コマンドはこのように。

# phpmd [ファイル名] [レポート形式] [ルール]

ファイル名は解析したいファイル名、レポートは出力したいファイル名で、text と xml で出力できるみたいです。ルールは5つあるようですが、今回は unusedcode をセットしてみました。これは使われていないコードを検出してくれます。クラス内で定義はされてるけど参照されてない変数、パラメータなどを抽出してくれます。typo とかこれで発見できるかなあと思って導入しています。ちなみにその他のルールはこんな感じです。

  • controversial: キャメルケースなどを検出する
  • codesize: コードサイズ関連部分を検出する
  • design: 設計の問題を検出する
  • naming: 名前が短い、長い箇所を検出する

実際に実行してみるとこんな感じになります。

$ phpmd hogehoge.php text unusedcode
 
hogehoge.php:1225 Avoid unused local variables such as '$session'.
hogehoge.php:1674 Avoid unused parameters such as '$html'.
                  :
                  : (こんな感じでずらずらと…)

うん。かなり出てきました!コードをじっくり確認しないと発見できないような部分も発見!

これを開発側に投げて、確認して修正して貰うようにしています。これでもう少し早くバグが見つかると思います!

初回は全部1つずつ確認しましたが、問題ない部分はルールのカスタマイズをしたりすれば検出されなくなるらしいです。自分はルールのカスタマイズをするのが面倒だったので、確認した次の日からは新規に検出された部分だけ確認するようにしています。また、このチェックは日次で自動化して実行しないと手間かかっちゃいますので、自分は Jenkins にプロジェクトを作成して実行させるようにしています。

この ①PHPMD の Jenkins へ導入、②新規に検出された部分だけ確認するような運用方法については次回の投稿で紹介します!

参考サイト: CentOS6.3で静的解析ツールPHPMDを動かしてみる(PHP用)

Tags: , ,

nishigo

今年のワールドカップはドイツが優勝すると信じてやまないnishigoです。

最近は開発でもっぱらJavaScriptしか触っていません。
普段はPHPでコードを書くことのほうが多いので、私のようにJavaScriptは
どうしてもprototype.js等のライブラリに頼りきってしまう、
なんて方も多いのではないでしょうか。

そんな苦手意識を少しでも克服する為に、
あまり取り上げられることのないコード統一化TipsをJavaScriptの特性に触れながら
解説していきます。
続きを見る >>>

Tags: , ,

suzuki

だいぶご無沙汰しましたが、iPad を買って、毎日自分の指紋を確認している suzuki です。ぺたぺた。

みなさん「コンプライアンス」って言葉、聞いた事ありますか? 「法律遵守」とか「法的準拠」とかという意味に使われたりします。まぁ、要するに「会社として、国や自治体のルールなどは守らないとね!」という話です。

ルールを守る為には、ルールを知る必要があります。また、ルールは状況に応じて変わって行きますので、変更があったかどうかを知っておく必要もあります。ルールが変わる前に告知されますので、そこで気がつくはずですが、気付かずにスルーしてしまうことも考えられます。

そんな場合に利用できるのが、「電子政府の総合窓口 イーガブ」内にある「法令データ提供システム」です。法令の名称を入れると、その内容を確認できます。また、最終改正の日付が分かるので、過去に調べた時点から変更があった可能性を探ることができます。(実際の変更点は、追って調べる必要がありますが。。。)

トライコーンでは、実際にこういった法令の変更に対して調査し、変更があれば会社内のルールの見直しなどを行なっています。

と、その役割を今年は私がやっていたのですが、毎回フォームから入力して調べるのが面倒だなぁと思って、最終改正日を取得するスクリプトを作成してみました。

続きを見る >>>

Tags: ,

toda

PHPでなんちゃってMixinを実現してみる

Posted Date: 2009-10-23 17:53
Author: / No Comments

秋も深くなってきました。秋の夜長いかがお過ごしですか?
今週末はTOEIC試験を控えていますが、何にも勉強はしておりません。とりあえず、自分の英語力がどれだけ低いのかを確認してきたいと思います。こんにちわ。todaです。

最近、まつもとゆきひろさんご著書の「まつもとゆきひろ コードの世界」を拝読しました。rubyの設計・開発を手掛けた方だけあって、言語についての深い知識と考察や歴史が学べて面白かったです。

今回は、その中で気を引いた、”mixin”という多重継承のスマートな使い方をPHPで実現できないかという実験をしてみます。
続きを見る >>>

Tags: , , , , , ,