PHPプロ!TIPS+

1. stdinからのメール処理でメール情報を取得する

最近は携帯サイトなどで、特定のメールアドレスにメールを送信するとそのサイトの日記として、登録されるようなものをよく見かけます。
今回はPEAR::MailのmimeDecode.phpを使用して、送信されてきたメールのタイトル、本文、添付ファイル(画像)を取得する方法をご紹介します。

まずは、今回のTipsではメールの詳細な設定(postfix,qmailのエイリアス設定など)は省略させていただきます。

エイリアス例を基に説明させていただきます。

例:
diary:      "|/usr/local/bin/php /***/****/diary.php"

上記では「diary@***.jp」のようなメールアドレスにメールを送信すると、 「/***/****/diary.php」を実行するような設定になっています。

実装内容について

  1. 1. stdinでメールの内容を読み込みます。
  2. 2. 読み込んだ内容をPEAR::MailのmimeDecodeでメールの内容を分解します。
  3. 3. 分解した内容からタイトル、本文、添付ファイルを取り出します。

下記が実装例のスクリプトになります。

<?php
  
require_once 'Mail/mimeDecode.php';
  
// メールデータ取得
  
$params['include_bodies'] = true;
  
$params['decode_bodies']  = true;
  
$params['decode_headers'] = true;
  
$params['input'] = file_get_contents("php://stdin");
  
$params['crlf'] = "\r\n";
  
$structure Mail_mimeDecode::decode($params);

  
//送信者のメールアドレスを抽出
  
$mail $structure->headers['from'];
  
$mail addslashes($mail);
  
$mail str_replace('"','',$mail);

  
//署名付きの場合の処理を追加
  
preg_match("/<.*>/",$mail,$str);
  if(
$str[0]!=""){
    
$str=substr($str[0],1,strlen($str[0])-2);
    
$mail $str;
  }
  
/*
   *「$structure->headers['to']」で送信元のメールアドレスも取得できます。
   */

  // 件名を取得
  
$diary_subject $structure->headers['subject'];
  
  switch(
strtolower($structure->ctype_primary)){
    case 
"text"// シングルパート(テキストのみ)
      
$diary_body $structure->body;
      break;
    case 
"multipart":  // マルチパート(画像付き)
      
foreach($structure->parts as $part){
        switch(
strtolower($part->ctype_primary)){
          case 
"text"// テキスト
            
$diary_body $part->body;
            break;
          case 
"image"// 画像
            //画像の拡張子を取得する(小文字に変換
            
$type strtolower($part->ctype_secondary);
            
//JPEGチェック(GIFやPNG形式の画像チェックなども可
            
if($type != "jpeg" and $type != "jpg"){
              continue;
            }
            
//添付内容をファイルに保存
            
$fp fopen("/tmp/picture.jpg" $type,"w" );
            
$length strlen$part->body );
            
fwrite$fp$part->body$length );
            
fclose$fp );
            break;
        }
      }
    break;
    default:
    
$diary_body "";
  }
  
/*
   * 取得したメールアドレス、タイトル、本文、画像を使用してデータベースなどに取り込む
   */
?>

上記のスクリプトで格納されていている変数の説明をします。

  • $diary_subjectにはメールのタイトルが格納されています。
  • $diary_bodyにはメール本文が格納されています。
  • 「/tmp/picture.jpg」には添付した画像が保存されます。

上記の内容に加えて、Fromのメールアドレスからユーザ情報と結びつけることができます。これで特定のメールアドレスにメールを送信するだけで、日記としてデータベー スなどに登録することができますね。

ここでは日記として説明しましたが、携帯ではPCのように<input type="file">を対応している機種が少ないので、携帯からの画像UPLOADの用途として使用することもできます。

このようにメールと組み合わせることによって、いろいろな用途で使うことができると思います。

2. 比較の落とし穴

PHPで変数などの比較をする際、おのおのが別の型同士だった場合にちょっと躓きやすい落とし穴があるのをご存知でしょうか?
PHPに少しずつ慣れてきた人は、比較する時に起こる型変換のルールを知っておくと未然にミスが防げたりするかも知れません。

まずは簡単に。以下のスクリプトを実行した際の表示内容はどうなるでしょう?

<?php
    $foo 
"test";

    if (
$foo == 0) {
        print 
"true";
    } else {
        print 
"false";
    }
?>

「true」と答えた方は、今回のTipsの趣旨は既にご理解なさっている方です。今回のTipsはご確認程度で見てもらえればと思います。
もし「false」と答えた方は、是非今回の落とし穴を知っていって下さい。ちなみに上のスクリプトの実行結果では「true」と表示されます。

では何故こうなるのでしょう?

その答えは、比較部分にあります。

上のスクリプトでは、$fooに代入されている文字列 'test' と整数の 0 を比較しています。
型的に言えば、$fooは文字列のstring、0は整数のintegerです。PHPでは以下のルールがあります。

整数値を文字列と比較する際、文字列が数値に変換されます。

『PHPマニュアル:比較演算子』
http://www.php.net/manual/ja/language.operators.comparison.php

さて、ここで言う「数値に変換される」という部分が問題です。
詳しいルールは以下の参考URLからご覧いただくとしまして、今回関係する部分を大雑把に書きますと

「文字列の最初の部分が、有効な数値データで始まるもの以外は 0 となる」

となります。

『PHPマニュアル:文字列の変換』
http://www.php.net/manual/ja/language.types.string.php#language.types.string.conversion

"test" という文字列は、最初の部分が英文字の t ……すなわち数字としては有効では無いので実際に比較する際には 0 として扱われるのです。

比較時のこの決まりを知らないと、意図していなかった動きをする場合が出てきます。マニュアル内でもswitch文の例が挙げられていますが

<?php
    $foo 
"test";

    switch (
$foo) {
        case 
0:
            print 
'$fooは 0 です。';
            break;
        case 
"test":
            print 
'$fooは test です。';
            break;
        default:
            print 
'$fooは 0 でも test でもありません。';
    }
?>

最初に$fooに"test"という文字列を代入します。

「$fooは test です。」

と表示される事を意図してこのようにプログラムコードを書いたとしても、実際には

「$fooは 0 です。」

と表示されます。

文字列と整数を比較しているため、文字列が数値に変換され、0として扱われるためです。

一方、

case 0:

と書いている部分を

case '0':

などと書くと、これは「文字列」の 0 を示すため、文字列と文字列の比較が行われます。

この時、比較する型が一緒のため数値に変換されるという事は無く、純粋にそのまま比較が行われるためこの部分では一致せず、次の「 case "test": 」の所で真になり「$fooは test です。」と表示されます。

もう一つ同じような例を示します。

<?php
    $ary 
array(012);

    
$foo "test";

    if (
in_array($foo$ary)) {
        print 
"発見!";
    } else {
        print 
"見つかりませんでした…";
    }
?>

in_array関数は、配列に値があるかチェックする関数です。
上の例では test という文字列が $ary の中に値として存在するかチェックしています。

普通に考えれば「見つかりませんでした…」と表示したいハズなのですが実際に動かしてみればわかりますが「発見!」と表示されてしまいます。
配列の中に整数の 0 があるため、数値変換された際に0になる文字列はこぞって引っかかってしまう事になります。

余談ですが、このin_array関数もそうですがいくつかの関数は、引数を追加して指定する事で比較時に「型」も確認します。
この場合、完全に型までも一緒でなければいけなくなり、判定を厳格にする事ができます。

以下、型も比較する例です。

<?php
    $ary 
array(012);

    
$foo "test";

    if (
in_array($foo$arytrue)) {
        print 
"発見!";
    } else {
        print 
"見つかりませんでした…";
    }
?>

このようにすると実行結果の出力は「見つかりませんでした…」となります。

バックナンバーについて

TIPS-MLは、毎週金曜日に更新され、新しい記事が掲載されます。

Tipsꗗy[W 

Pick Up Q&A

Q
ログファイルの中の空のデータ行を削除したい
 このエントリーをはてなブックマークに追加 
A
ログのデータ個数(列数)が固定で、空のログが"<><><>"だと既知であれば if ($line === "<><><>") { continue; } で読み飛ばしてもいいのでは? ...

>>続きを読む

まずは配列や文字列の扱いから、じっくり勉強して行きましょう。

▲解説者:岡本(アシアル株式会社 教育コーディネーター兼 システムエンジニア)