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: , , , ,