PHPでログイン機能を実装するチュートリアル #9



1. [基本設計、ユーザーモデル](http://qiita.com/ShibuyaKosuke/items/f114ffccf441edb2b745)
2. [オートローダー](http://qiita.com/ShibuyaKosuke/items/6109810464a7293293bd)
3. [例外・ログ](http://qiita.com/ShibuyaKosuke/items/88b1305a4ff0c5f3e728)
4. [PDO シングルトン　SQLインジェクション](http://qiita.com/ShibuyaKosuke/items/094df7f399912ae7df56)
	* [汎用データベース・クラス作成](http://qiita.com/ShibuyaKosuke/items/8502492ac024362b528d)

5. [ユーザーモデルの作成](http://qiita.com/ShibuyaKosuke/items/81c556d836d70a29c5f3)
6. [クラスの継承](http://qiita.com/ShibuyaKosuke/items/490ffa6a92670cf6c644)
7. [テンプレート・クラスの実装](http://qiita.com/ShibuyaKosuke/items/803dc58a23601118942e)
8. [アカウントロック機能の実装](http://qiita.com/ShibuyaKosuke/items/e890becdb8193d3def05)
9. メール送信機能の実装
10. [アカウントロック解除機能の実装](http://qiita.com/ShibuyaKosuke/items/ee4b1896071eeca37628)
11. [CSRF対策](http://qiita.com/ShibuyaKosuke/items/8100e304e4447e44e98c)

##はじめに

　メール送信については、多くの入門書席にあるようなサンプルコードではうまくいかないことが多い。かつて[このような記事](http://qiita.com/ShibuyaKosuke/items/309c0a7d969baf0ea8d1)も書かせてもらいました。

　要点としては **ちゃんとSMTPサーバーを通してメール送信しましょう** ということだ。
　
　外部ライブラリに [PHPMailer](https://github.com/PHPMailer/PHPMailer)を利用しています。
　composer.jsonに以下を記述し、コマンド `composer update` でインストールできます。

```json:composer.json
{
	"name": "MyApp",
	"description": "login",
	"require": {
		"php": ">=5.6.0",
		"phpmailer/phpmailer": "~5.2",
		"smarty/smarty": "~3.1"
	}
}
```

**利用しやすく、記述は簡単に** がモットーなので、以下のように利用できるように設計します。

```php
Mail::send($email, $title, $body);
```

`Mail.class.php`を作成します。

* 要件にはないが、添付ファイル対応
* メール本文にテンプレートを利用できるようにした。

　サービスが運用フェーズに入ると、意外と送信メールの書式を変更する場面が出てくる。その度にソースコードをGREPし、ファイルを探し、ヒアドキュメントを修正し…なんてことは非常に面倒。

　テンプレートエンジンを使うことで、HTMLだけではなく、こういったメールテンプレート機能にも応用できます。
　ポイントとしては、通常の `display()` ではなく、`fetch()` を使います。`fetch()` の場合、返り値をブラウザには返すことなく、メモリ上に確保します。
　
　リファレンス
　[fetch()](http://www.smarty.net/docs/ja/api.fetch.tpl)

テンプレートを利用した記述は以下のようになります。

```php
$email = 'recipient@example.com';
$title = 'メール・タイトル';
$body = Mail::getTemplate('template.tpl', array('name'=>'おなまえ'));
Mail::send($email, $title, $body);
```

##実装

```php:classes/common/Mail.class.php
<?php

namespace MyApp\common;

/**
 * Mailer.php
 * @since 2015/07/24
 */
class Mail
{

	/**
	 * 送信専用メール
	 */
	const SEND_ONLY_EMAIL = "送信用メールアドレス";

	/**
	 * 送信専用メール　表示名
	 */
	const SEND_ONLY_EMAIL_NAME = 'MyApp';

	/**
	 * 送信専用メール　言語名
	 */
	const SEND_ONLY_EMAIL_LANGUAGE = "japanese";

	/**
	 * 送信専用メール　文字コード
	 */
	const SEND_ONLY_EMAIL_ENCODING = 'utf-8';

	/**
	 * ホスト名
	 */
	const MAIL_HOST = 'mail.example.com';

	/**
	 * ポート
	 */
	const MAIL_PORT = '25';

	/**
	 * ユーザー名
	 */
	const MAIL_USER = 'contact@example.com';

	/**
	 * パスワード
	 */
	const MAIL_PASS = 'smtp_password';

	/**
	 * メールを送信する
	 * @param string $strRecipient 宛先
	 * @param string $strSubject 題名
	 * @param string $strBody 本文
	 * @param array('path'=>'', 'name'=>'') $binAttachment 添付ファイル
	 * @throws \Exception
	 */
	public static function send($strRecipient, $strSubject, $strBody, array $binAttachment = [])
	{
		Log::write('*** send_mail ***');

		if (empty($strRecipient)) {
			throw new \Exception('宛先が設定されていません。');
		}
		if (empty($strSubject)) {
			throw new \Exception('メールタイトルが設定されていません。');
		}
		if (empty($strBody)) {
			throw new \Exception('メール本文が設定されていません。');
		}

		mb_language(self::SEND_ONLY_EMAIL_LANGUAGE);
		mb_internal_encoding(self::SEND_ONLY_EMAIL_ENCODING);

		$from = self::SEND_ONLY_EMAIL;
		$fromname = self::SEND_ONLY_EMAIL_NAME;

		$mail = new \PHPMailer();

		$mail->IsSMTP();
		$mail->SMTPAuth = TRUE;
		$mail->Host = self::MAIL_HOST;
		$mail->Port = self::MAIL_PORT;
		$mail->Username = self::MAIL_USER;
		$mail->Password = self::MAIL_PASS;

		$mail->CharSet = "iso-2022-jp";
		$mail->Encoding = "7bit";

		$mail->AddAddress($strRecipient);
		$mail->From = $from;
		$mail->FromName = mb_encode_mimeheader(
			mb_convert_encoding(
				$fromname
				, "JIS"
				, self::SEND_ONLY_EMAIL_ENCODING
			)
		);
		$mail->Subject = mb_encode_mimeheader($strSubject);
		$mail->Body = mb_convert_encoding(
			$strBody
			, "JIS"
			, self::SEND_ONLY_EMAIL_ENCODING
		);

		foreach ($binAttachment as $attachment) {
			if (array_key_exists('path', $attachment) &&
				array_key_exists('name', $attachment) &&
				file_exists($attachment['path'])) {

				$mail->AddAttachment(
					$attachment['path']
					, $attachment['name']
				);
			} else if (array_key_exists('path', $attachment) &&
				file_exists($attachment['path'])) {

				$mail->AddAttachment($attachment['path']);
			} else if (file_exists($attachment)) {

				$mail->AddAttachment($attachment);
			}
		}

		if (!$mail->Send()) {
			throw new \Exception($mail->ErrorInfo);
		}
	}

	/**
	 * メールテンプレートに変数を割り当てて本文を取得
	 * @param string $templateName
	 * @param array $args
	 */
	static public function getTemplate($templateName, array $args = [])
	{
		$smarty = new \Smarty();
		$smarty->setTemplateDir(BASE_DIR . '/smarty/templates/mail/');
		foreach ($args as $k => $v) {
			$smarty->assign($k, $v);
		}
		return $smarty->fetch($templateName);
	}

}