Archives by date

You are browsing the site archives by date.

PHPで無名関数を使ってみる。

Javascriptでおなじみの無名関数をPHPでも使えるみたいだったので遊んでみた。※PHP5.3以上に限る

とりあえずいつもどおり書いてみる。

$a = 'Hello ';
		
$noname = function($a = null) {
	return $a . 'world!!';
};

// 変数を渡さない場合
$res = $noname();
var_dump($res);
↓
string(7) "world!!"

// 変数を渡した場合
$res = $noname($a);
var_dump($res);
↓
string(13) "Hello world!!"

ちょっと感動。
そして下記例のように「use」を用いることで、無名関数の外にあるスコープの変数を無名関数内に渡すことが可能。
ここでは分かりやすく単なる変数で実験。

$a = 'Hello ';
$b = 'my ';

$noname = function($b = null) use ($a) {
	return $a . $b . 'world!!';
};

// 関数実行時に変数を渡さなくても、$aの'Hello 'は内部から使用することができる。
$res = $noname();
var_dump($res);
↓
string(13) "Hello world!!"

// 実行時変数を渡せばもちろん以下の結果になる。
$res = $noname($b);
var_dump($res);
↓
string(16) "Hello my world!!"

これはおもしろい。再帰処理とかコールバックとかいろいろ楽しいことができそうな予感。積極的に使ってみよう。

PHPでlist()関数を使ってみる。

この表現を用いると、複数の変数へ一度に値を代入することが出来る。
例えば以下のような感じ。

$params = array('hoge', 'fuga', 'piyo');

list($param1, $param2, $param3) = $params;

var_dump($param1, $param2, $param3);
↓
string(4) "hoge" string(4) "fuga" string(4) "piyo"

関数で受け取った値を一気に変数へ代入できたりする。1行で(重要)。

protected function func($p1, $p2, $p3) {
	list($get1, $get2, $get3) = func_get_args();
}

ゆうてそんなに使う場面なさそう。笑

PHPで変数を有効な PHPコードとして取得する方法。

めも。

$vars = array(
	'param1' => 'hoge',
	'param2' => 'fuga',
	'param3' => 'piyo'
);

var_export($vars);
↓
array ( 'param1' => 'hoge', 'param2' => 'fuga', 'param3' => 'piyo', )

var_export()は第2引数にtrueを渡すことで、標準出力ではなく文字列を返却してくれる。

$vars = array(
	'param1' => 'hoge',
	'param2' => 'fuga',
	'param3' => 'piyo'
);

$res = var_export($vars, true);

var_dump($res);
↓
string(75) "array ( 'param1' => 'hoge', 'param2' => 'fuga', 'param3' => 'piyo', )"

var_export()で得られる文字列は有効なPHPコードなので、当然そのまま貼り付けても動作してくれる。

PHPでURLを解析してみる。

与えられたURLを解析して、ホスト名やドメイン、IPアドレスなどを取得したい時用。

$url = 'http://example.com/';
$res = parse_url($url);

var_dump($res);
↓
array(3) {
	["scheme"]=>
	string(4) "http"
	["host"]=>
	string(11) "example.com"
	["path"]=>
	string(1) "/"
}

parse_url()を使うとそのURLから特定できる情報を配列で返してくれる。
続けてIPアドレスを取得したい場合は以下の通り。

$ip = gethostbyname($res['host']);
		
var_dump($ip);
↓
string(14) "93.184.216.119"

gethostbyname()に上部で取得したホスト名を渡してやることでIPアドレスに変換してくれる。

以下おまけ。

ドメインを取得する。

$domain = end(explode('.', $res['host']));

var_dump($domain);
↓
string(3) "com"

PHPのmb_encode_mimeheader()で文字化けを回避する。

mb_encode_mimeheader()で文字列を変換する際は、事前にmb_internal_encoding()にてエンコードを統一しておかなければならない。

ということで以下のような処理が必要になる。

※メール送信クラスより引用

protected function mem($var) {
	// 関数の存在チェック
	$internalEncoding = function_exists('mb_internal_encoding');
	if($internalEncoding) {
		// 現在のエンコード設定を保存
		$restore = mb_internal_encoding();
		// 変換したい文字エンコードを設定
		mb_internal_encoding('UTF-8');
	}
	// 文字列を変換
	$return = mb_encode_mimeheader($var, 'UTF-8', 'B');
	// 処理後、変更していた文字コードを戻す
	if($internalEncoding) mb_internal_encoding($restore);
	// 値を返却
	return $return;
}

mb_encode_mimeheader()第3引数の’B’は「Base64」エンコードという意味。
‘Q’を指定すると「Quoted-Printable」になる。
デフォルトは’B’。

まぁ実際、関数の存在チェックまではやらなくてもいいと思う(適当)。

PHPでファイルパスからファイル名を取得する。

最後のDS以降のファイル名がサクっとほしい時に使用。

例。

$path = '/home/admin/../../targetFile.php';
echo basename($path);
↓
targetFile.php

ちなみに以下の記述で自分のファイル名が得られる。

echo basename(__FILE__);
↓
targetFile.php

まぁ当然なんだけども。

PHPにて相対パスを絶対パスに変換する。

めも。

例えば以下のような感じ。

$path = '../../path/to/file.ext'
echo realpath($path);
↓
/home/admin/...../path/to/file.ext

自分用メール送信クラスに添付ファイル送信機能をつけた。

以前の記事←で作成したメール送信クラスに添付ファイル送信機能をつけてみた。

使える全メソッドは下部で解説。
簡単な使い方は関数上部のコメントアウトを参照されたし。

<?php
/**
 * sendmailを用いたシンプルなメール送信用クラス
 * 単純に送信したい場合は以下の通り
 * ※to, cc, bcc, のいずれか一つの指定は必須
 * 
 * $mail = new simpleMailTransmission();
 * 		
 * $res = $mail
 * ->to('to@local.host') // 必要に応じて
 * ->cc('cc@local.host') // 必要に応じて
 * ->Bcc('bcc@local.host') // 必要に応じて
 * ->attachments('/path/to/file.ext') // 添付ファイル設定
 * ->from('from@local.host') // 必須
 * ->subject('タイトルを指定')
 * ->send('本文を指定');
 * 
 * ※添付ファイルの詳細設定は、attachments()関数上部のコメントアウトを参照
 * 
 */
class simpleMailTransmission {
	
	// 送信先
	protected $to = array();
	
	// メールタイトル
	protected $subject = '';
	
	// メール本文
	protected $message = '';
	
	// ヘッダー情報
	protected $header = '';
	
	// 送信先一時格納変数(複数可)
	protected $_to = array();
	
	// 送信元設定
	protected $_from = array();
	
	// Cc格納変数
	protected $_cc = array();

	// Bcc格納変数
	protected $_bcc = array();

	// ヘッダー情報一時格納変数
	protected $_header = '';
	
	// テンプレートファイルパス
	protected $_template = '';
	
	// テンプレートに渡す変数
	protected $_viewVars = array();
	
	// 添付ファイル
	protected $_attachments = array();

	// 添付ファイル送信用バウンダリヘッダ
	protected $_boundary = null;
	
	// 言語設定
	protected $_lang = 'ja';
	
	// エンコーディング
	protected $_encoding = 'UTF-8';
	
	// キャラセット
	protected $_charset = 'ISO-2022-JP';
	
	// 送信時文字エンコード
	protected $_transferEncoding = '7bit';
	
	// 送信ヘッダー内「X-Mailer」設定
	protected $_xMailer = 'GEKIOKOPUNPUNMARU';
	
	/**
	 * 送信先のセット
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function to($email = null, $name = null) {
		if($email === null) {
			return $this->_to;
		}
		return $this->_setEmail('_to', $email, $name);
	}
	
	/**
	 * 送信先の追加
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	public function addTo($email, $name = null) {
		return $this->_addEmail('_to', $email, $name);
	}

	/**
	 * Ccのセット
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function cc($email = null, $name = null) {
		if($email === null) {
			return $this->_cc;
		}
		return $this->_setEmail('_cc', $email, $name);
	}
	
	/**
	 * Ccの追加
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	public function addCc($email, $name = null) {
		return $this->_addEmail('_cc', $email, $name);
	}
	
	/**
	 * Bccのセット
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function bcc($email = null, $name = null) {
		if($email === null) {
			return $this->_bcc;
		}
		return $this->_setEmail('_bcc', $email, $name);
	}
	
	/**
	 * Bccの追加
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	public function addBcc($email, $name = null) {
		return $this->_addEmail('_bcc', $email, $name);
	}
	
	/**
	 * セット用メソッド(配列可)
	 * @param unknown_type $varName
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	protected function _setEmail($varName, $email, $name) {
		if(is_array($email)) {
			$list = array();
			foreach($email as $key => $value) {
				if(is_int($key)) {
					$key = $value;
				}
				if(self::email($key)) {
					$list[$key] = $value;
				}
			}
			$this->{$varName} = $list;
			return $this;
		}
		if(self::email($email)) {
			if($name === null) {
				$name = $email;
			}
			$this->{$varName} = array($email => $name);
		}
		return $this;
	}
	
	/**
	 * 追加用メソッド(配列可)
	 * @param unknown_type $varName
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	protected function _addEmail($varName, $email, $name) {
		if(is_array($email)) {
			$list = array();
			foreach($email as $key => $value) {
				if(is_int($key)) {
					$key = $value;
				}
				if(self::email($key)) {
					$list[$key] = $value;
				}
			}
			$this->{$varName} = array_merge($this->{$varName}, $list);
			return $this;
		}
		if(self::email($email)) {
			if($name === null) {
				$name = $email;
			}
			$this->{$varName}[$email] = $name;
		}
		return $this;
	}
	
	/**
	 * 送信元の設定
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return multitype:|mailTerminal
	 */
	public function from($email = null, $name = null) {
		if($email === null) {
			return $this->_from;
		}
		return $this->_setEmailSingle('_from', $email, $name);
	}
	
	/**
	 * 単一の値であることを保障するためのセット関数
	 * @param unknown_type $varName
	 * @param unknown_type $email
	 * @param unknown_type $name
	 * @return mailTerminal
	 */
	protected function _setEmailSingle($varName, $email, $name) {
		$current = $this->{$varName};
		$this->_setEmail($varName, $email, $name);
		if(count($this->{$varName}) !== 1) {
			$this->{$varName} = $current;
		}
		return $this;
	}
	
	/**
	 * メールタイトルのセット
	 * @param unknown_type $subject
	 * @return string|mailTerminal
	 */
	public function subject($subject = null) {
		if($subject === null) {
			return $this->subject;
		}
		$this->subject = (string)$subject;
		return $this;
	}
	
	/**
	 * テンプレートファイルパスの指定
	 * @param unknown_type $template
	 * @return string|mailTerminal
	 */
	public function template($template = false) {
		if($template === false) {
			return $this->_template;
		}
		$this->_template = $template;
		return $this;
	}
	
	/**
	 * テンプレートファイルに渡す変数を設定
	 * @param unknown_type $viewVars
	 * @return multitype:|mailTerminal
	 */
	public function viewVars($viewVars = null) {
		if($viewVars === null) {
			return $this->_viewVars;
		}
		$this->_viewVars = array_merge($this->_viewVars, (array)$viewVars);
		return $this;
	}
	
	/**
	 * 添付ファイルの設定
	 * 
	 * $mail->attachments('/path/to/file.ext');
	 * 
	 * $mail->attachments(array('customName.ext' => '/path/to/file.ext'));
	 * 
	 * $mail->attachments(array('customName.ext' => array(
	 *		'file' => '/path/to/file.ext',
	 *		'mimetype' => 'image/jpg',
	 *		'contentId' => 'qwerty',
	 *		'contentDisposition' => false
	 * ));
	 * 
	 */
	public function attachments($attachments = null) {
		if($attachments === null) {
			return $this->_attachments;
		}
		$attach = array();
		foreach((array)$attachments as $name => $fileInfo) {
			if(!is_array($fileInfo)) {
				$fileInfo = array('file' => $fileInfo);
			}
			if(empty($fileInfo['file'])) {
				return 'File not specified.';
			}
			$fileInfo['file'] = realpath($fileInfo['file']);
			if($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
				return 'File not found.';
			}
			if(is_int($name)) {
				$name = basename($fileInfo['file']);
			}
			if(!isset($fileInfo['mimetype'])) {
				$fileInfo['mimetype'] = 'application/octet-stream';
			}
			$attach[$name] = $fileInfo;
		}
		$this->_attachments = $attach;
		return $this;
	}
	
	/**
	 * 添付ファイルの追加
	 * @param unknown_type $attachments
	 * @return simpleMailTransmission
	 */
	public function addAttachments($attachments) {
		$current = $this->_attachments;
		$this->attachments($attachments);
		$this->_attachments = array_merge($current, $this->_attachments);
		return $this;
	}
	
	/**
	 * ヘッダーバウンダリの生成
	 * ※要添付ファイル
	 */
	protected function _createBoundary() {
		if (!empty($this->_attachments)) {
			$this->_boundary = md5(uniqid(time()));
		}
	}
	
	/**
	 * 添付ファイルがある場合、
	 * メッセージにバウンダリーを挿入
	 * @return string
	 */
	protected function _addBoundaries() {

		$this->_createBoundary();
		$msg = '';
	
		$contentIds = array_filter((array)self::_extract($this->_attachments, '{s}.contentId'));
		$hasInlineAttachments = count($contentIds) > 0;
		$hasAttachments = !empty($this->_attachments);
		
		$boundary = $relBoundary = $textBoundary = $this->_boundary;
	
		if ($hasInlineAttachments) {
			$msg .= '--'.$boundary."\n";
			$msg .= 'Content-Type: multipart/related; boundary="rel-'.$boundary.'"'."\n\n";
			$relBoundary = $textBoundary = 'rel-'.$boundary;
		}
		if (isset($this->message)) {
			if ($textBoundary !== $boundary || $hasAttachments) {
				$msg .= '--'.$textBoundary."\n";
				$msg .= 'Content-Type: text/plain; charset='.$this->_charset."\n";
				$msg .= 'Content-Transfer-Encoding: '.$this->_transferEncoding."\n\n";
			}
			$msg .= $this->message."\n\n";
		}
		if ($hasInlineAttachments) {
			$attachments = $this->_attachInlineFiles($relBoundary);
			$msg .= $attachments."\n";
			$msg .= '--'.$relBoundary.'--'."\n\n";
		}
		if ($hasAttachments) {
			$attachments = $this->_attachFiles($boundary);
			$msg .= $attachments."\n";
		}
		if($hasAttachments) {
			$msg .= '--'.$boundary.'--'."\n\n";
		}
		return $msg;
	}
	
	/**
	 * コンテンツIDを持つ添付ファイルの追加
	 * @param unknown_type $boundary
	 * @return multitype:string unknown
	 */
	protected function _attachInlineFiles($boundary) {
		$msg = '';
		foreach($this->_attachments as $filename => $fileInfo) {
			if(empty($fileInfo['contentId'])) continue;
			$data = $this->_readFile($fileInfo['file']);
			$msg .= '--'.$boundary."\n";
			$msg .= 'Content-Type: '.$fileInfo['mimetype']."\n";
			$msg .= 'Content-Transfer-Encoding: base64'."\n";
			$msg .= 'Content-ID: <'.$fileInfo['contentId'].'>'."\n";
			$msg .= 'Content-Disposition: inline; filename="'.$filename.'"'."\n\n";
			$msg .= $data."\n\n";
		}
		return $msg;
	}
	
	/**
	 * コンテンツIDを持たない添付ファイルの追加
	 * @param unknown_type $boundary
	 * @return multitype:string
	 */
	protected function _attachFiles($boundary) {
		$msg = '';
		foreach($this->_attachments as $filename => $fileInfo) {
			if (!empty($fileInfo['contentId'])) continue;
			$data = $this->_readFile($fileInfo['file']);
	
			$msg .= '--'.$boundary."\n";
			$msg .= 'Content-Type: '.$fileInfo['mimetype']."\n";
			$msg .= 'Content-Transfer-Encoding: base64'."\n";
			if (!isset($fileInfo['contentDisposition']) || $fileInfo['contentDisposition']) {
				$msg .= 'Content-Disposition: attachment; filename="'.$filename.'"'."\n\n";
			}
			$msg .= $data."\n\n";
		}
		return $msg;
	}
	
	/**
	 * 添付するファイルの読み込み(エンコード)
	 * @param unknown_type $path
	 * @return string
	 */
	protected function _readFile($path) {
		return chunk_split(base64_encode(file_get_contents($path)));
	}
	
	/**
	 * メールアドレスのバリデーション設定
	 * @param unknown_type $check
	 * @return boolean
	 */
	protected function email($check) {
		$hostName = '(?:[_a-z0-9][-_a-z0-9]*\.)*(?:[a-z0-9][-a-z0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})';
		$regex = '/^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@'.$hostName.'$/i';
		return self::_check($check, $regex);
	}
	
	/**
	 * メールの送信開始トリガー
	 * @param unknown_type $plainMessage
	 * @return string|Ambigous <boolean, multitype:boolean >
	 */
	public function send($plainMessage = null) {
		if(empty($this->_from)) {
			return 'From is not specified.';
		}
		if(empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
			return 'You need to specify at least one destination for to, cc or bcc.';
		}
		if(!file_exists($this->_template)) {
			$this->message = self::mce($plainMessage);
		} else {
			$this->message = self::mce($this->createMessage());
		}
		if(!empty($this->_attachments)) $this->message = $this->_addBoundaries();
		
		$this->header = $this->createHeader();
		$this->to = $this->createTo();

		return self::_send();
	}
	
	/**
	 * 送信処理
	 * @return boolean|multitype:boolean
	 */
	protected function _send() {
		mb_language($this->_lang);
		mb_internal_encoding($this->_encoding);
		
		if(!empty($this->_attachments)) return self::_sendWithAttachments();
		
		if(empty($this->to)) {
			return mb_send_mail(null, $this->subject, $this->message, $this->header);
		} else {
			$_errors = array();
			foreach($this->to as $to) {
				if(!mb_send_mail($to, $this->subject, $this->message, $this->header)) $_errors[$to] = false;
			}
			if(empty($_errors)) return true;
			return $_errors;
		}
	}
	
	protected function _sendWithAttachments() {
		$this->message = str_replace("\n", "\r\n", $this->message);
		$this->header = str_replace("\n", "\r\n", $this->header);
		if(empty($this->to)) {
			return mail(null, self::mem($this->subject), $this->message, $this->header);
		} else {
			$_errors = array();
			foreach($this->to as $to) {
				if(!mail($to, self::mem($this->subject), $this->message, $this->header)) $_errors[$to] = false;
			}
			if(empty($_errors)) return true;
			return $_errors;
		}
	}
	
	/**
	 * 追加ヘッダーの生成
	 * @return string
	 */
	protected function createHeader() {
		foreach($this->_from as $email => $name) $this->_header .= 'From: '.self::mem($name).' <'.$email.'>'."\n";
		if(!empty($this->_cc)) {
			$this->_header .= 'Cc: ';
			foreach($this->_cc as $email => $name) {
				$this->_header .= self::mem($name).' <'.$email.'>'.",";
			}
			$this->_header = self::trimLastChar($this->_header)."\n";
		}
		if(!empty($this->_bcc)) {
			$this->_header .= 'Bcc: ';
			foreach($this->_bcc as $email => $name) {
				$this->_header .= self::mem($name).' <'.$email.'>'.",";
			}
			$this->_header = self::trimLastChar($this->_header)."\n";
		}
		$this->_header .= 'X-Mailer: '.$this->_xMailer."\n";
		if(!empty($this->_attachments)) {
			$this->_header .= 'MIME-Version: 1.0'."\n";
			$this->_header .= 'Content-Type: multipart/mixed; boundary='.$this->_boundary."\n";
			$this->_header .= 'Content-Transfer-Encoding: '.$this->_transferEncoding."\n";
		}
		return $this->_header;
	}
	
	/**
	 * 送信先ヘッダーの生成
	 * @return multitype:string
	 */
	protected function createTo() {
		$t = array();
		if(!empty($this->_to)) {
			foreach($this->_to as $email => $name) {
				$t[] = self::mem($name).' <'.$email.'>';
			}
		}
		return $t;
	}
	
	/**
	 * テンプレートより本文の生成
	 * @return Ambigous <string, mixed>
	 */
	protected function createMessage() {
		$b = '';
		$tmp = file_get_contents($this->_template);
		if($tmp) {
			$varArray = array();
			if(!empty($this->_viewVars)) {
				foreach($this->_viewVars as $varName => $value) $varArray['{%'.$varName.'%}'] = $value;
			}
			$b = str_replace(array_keys($varArray) ,array_values($varArray), $tmp);
		}
		return $b;
	}
	
	/**
	 * バリデーションの実行
	 * @param unknown_type $check
	 * @param unknown_type $regex
	 * @return boolean
	 */
	protected function _check($check, $regex) {
		if(preg_match($regex, $check)) return true;
		return false;
	}
	
	protected function mem($var) {
		$internalEncoding = function_exists('mb_internal_encoding');
		if($internalEncoding) {
			$restore = mb_internal_encoding();
			mb_internal_encoding($this->_encoding);
		}
		$return = mb_encode_mimeheader($var, $this->_encoding, 'B');
		if($internalEncoding) mb_internal_encoding($restore);
		return $return;
	}
	
	protected function mce($var) {
		return mb_convert_encoding($var, $this->_charset);
	}
	
	protected function trimLastChar($var) {
		return substr($var, 0, -1);
	}
	
	/**
	 * 配列の中から指定した文字列を抽出する
	 * 
	 * - `{n}` Matches any numeric key, or integer.
	 * - `{s}` Matches any string key.
	 * 
	 * @param array $data
	 * @param unknown_type $path
	 * @return multitype:|array|Ambigous <multitype:multitype: , multitype:unknown >
	 */
	protected function _extract(array $data, $path) {
		if(empty($path) || !preg_match('/[{]/', $path)) return $data;
		$tokens = explode('.', $path);
		$_key = '__set_item__';
		$context = array($_key => array($data));
		foreach($tokens as $token) {
			$next = array();
			foreach($context[$_key] as $item) {
				foreach((array)$item as $k => $v) {	
					if(self::_matchToken($k, $token)) {
						$next[] = $v;
					}
				}
			}	
			$context = array($_key => $next);
		}
		return $context[$_key];
	}
	
	/**
	 * トークンに対するキーチェック
	 * @param unknown_type $key
	 * @param unknown_type $token
	 * @return boolean
	 */
	protected function _matchToken($key, $token) {
		if($token === '{n}') return is_numeric($key);
		if($token === '{s}') return is_string($key);
		return ($key === $token);
	}
}

用意した全メソッドの使い方は以下の通り。

$mail = new simpleMailTransmission();
$res = $mail
->to('to@local.host')
->addTo('addTo@local.host')
->addTo('addTo2@local.host')
->cc('cc@local.host')
->addCc('addCc@local.host')
->addCc('addCc2@local.host')
->bcc('bcc@local.host')
->addBcc('addBcc@local.host')
->addBcc('addBcc2@local.host')
->from('from@local.host')
->subject('mailSubjectHere.')
->template('/path/to/template.ext')
->viewVars($params)
->attachments('/path/to/file1.ext')
->addAttachments('/path/to/file2.ext')
->addAttachments('/path/to/file3.ext')
->send();

var_dump($res);

複数のTo、Cc、Bcc、添付ファイルの送信に対応。
テンプレートの文法、変数の渡し方は前回の記事を参照されたし。