素の PHP スクリプトに .env とエラーログ機能を足してみる

このごろは Laravel か WordPress を使った開発が多いので、フレームワークを使わない PHP を使うことはめっきり減ったのですが、ちょっとしたメールフォームくらいであれば素の PHP で簡単に作ってしまったほうが楽だったりします。そんなときに不便に感じるのが、設定周りとエラー処理。というわけで、素の PHP に設定周りとエラー処理をトッピングしてみることにします。

必要なパッケージのインストール

使うパッケージは、Laravel でも使われている vlucas/phpdotenvSeldaek/monolog を使います。重大なエラーが発生したときにメールで通知するようにしたいので、symfony/mailer を追加します。ということで、以下のコマンドでインストールします。

composer require vlucas/phpdotenv Seldaek/monolog symfony/mailer

.env の作成

ログ周りの設定については、.env に以下のような項目を作成しました。

HOSTNAME='www.example.com' # メール通知の見出しなどに使うホスト名
LOGFILE='/var/log/php_error.log' # ログファイルの置き場所
MAX_LOGFILES=14 # ログファイルの保存数。設定した数を超えると古いものから削除される。0 に設定すると無制限に保存される(デフォルト)

MAIL_HOST='example.com' # SMTP のホスト名
MAIL_USER='webmaster@example.com' # SMTP のアカウント名
MAIL_PASSWORD='password' # SMTP のパスワード
MAIL_PORT='587' # SMTP のポート番号
MAIL_OPTIONS='{"verify_peer":false}' # SSL(TSL)接続時に SSL サーバー証明書の検証を要求するかどうか。true が望ましいが、SSL 周りでエラーが出るときは false に設定
MAILER_DSN='smtp://webmaster%40example.com:password@example.com:587?verify_peer=0' # DSN 形式で書きたいときはこちらで。@ は %40 に置き換える必要があります

ERROR_REPORT_MAIL_FROM='webmaster@example.com' # エラー通知の FROM アドレス
ERROR_REPORT_MAIL_TO='user@example.com' # エラー通知の TO アドレス

設定とエラーログの共通処理

本題の共通処理部分はこんな感じで書いてみました。require_once のパスなどは適宜調整してください。

<?php
require_once __DIR__.'/vendor/autoload.php';

// 日本時間にしておかないと時刻がUTCになってしまう
date_default_timezone_set("Asia/Tokyo");

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SymfonyMailerHandler;
use Monolog\Logger;
use Monolog\ErrorHandler;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
use Symfony\Component\Mime\Email;

$logger = new Logger('local');

// PHP のエラーを Monolog で扱うための設定
ErrorHandler::register($logger);

// エラーをログに記録
// 日付ごとに1ファイル。$_ENV['LOGFILE'] に /var/log/php_error.log を指定すると
// php_error-2023-02-13.log のようなファイルが生成される
$logger->pushHandler(new RotatingFileHandler($_ENV['LOGFILE'], $_ENV['MAX_LOGFILES']));

// メール通知のための設定
$subject = 'エラーレポート(' . $_ENV['HOSTNAME'] . ')';

$email = (new Email())
    ->from($_ENV['ERROR_REPORT_MAIL_FROM'])
    ->to($_ENV['ERROR_REPORT_MAIL_TO'])
    ->subject($subject);

$dsn = new Dsn(
    'smtp',
    $_ENV['MAIL_HOST'],
    $_ENV['MAIL_USER'],
    $_ENV['MAIL_PASSWORD'],
    $_ENV['MAIL_PORT'],
    json_decode($_ENV['MAIL_OPTIONS'], true)
);
//$dsn = Dsn::fromString($_ENV['MAILER_DSN']); // DSN 形式を使いたいときはこちらで

$factory = new EsmtpTransportFactory();
$transport = $factory->create($dsn);
$mailer = new Mailer($transport);

// エラーレベルが ERROR 以上のエラーが発生したときはメール通知する
$logger->pushHandler(new SymfonyMailerHandler($mailer, $email, Logger::ERROR));

ちなみに、エラーログは以下のような感じになります。19行目で指定した文字(local)がログの中に記載されるので、複数のドメインを1つのサーバで扱っているような場合には、この部分をドメイン名にすることでログが識別しやすくなります。

[2023-02-13T17:53:24.835594+09:00] local.WARNING: E_WARNING: Undefined variable $description {"code":2,"message":"Undefined variable $foo","file":"/home/example/www/example.com/inquiry/thanks.php","line":8} []

あとは、このスクリプトを個々のスクリプトファイルの中で require_once すれば、機能を使うことができます。

できあがったスクリプトを見ると、簡単に書けているように見えますが、Monolog も Symfony Mailer も単独で使用する場合の使用例が少なく、意外に時間がかかりました。同じようなことをやりたい方の参考になればと思います。

おまけ:Symfony Mailer で SMTP を使ったメール送信をする

Monolog を使った場合、$email と $mailer を SymfonyMailerHandler に渡しますが、素の Symfony Mailer でメールを送信する場合はこんな感じになります。

<?php
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
use Symfony\Component\Mime\Email;

require_once 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$dsn = new Dsn(
    'smtp',
    $_ENV['MAIL_HOST'],
    $_ENV['MAIL_USER'],
    $_ENV['MAIL_PASSWORD'],
    $_ENV['MAIL_PORT'],
    json_decode($_ENV['MAIL_OPTIONS'], true)
);
//  $dsn = Dsn::fromString($_ENV['MAILER_DSN']);

$factory = new EsmtpTransportFactory();
$transport = $factory->create($dsn);
$mailer = new Mailer($transport);

$text_content = $_ENV['HOSTNAME'] . 'に Fatal Error が出ています。ログをご確認ください' . "\n";

$subject = 'エラーレポート(' . $_ENV['HOSTNAME'] . ')';

$email = (new Email())
    ->from($_ENV['ERROR_REPORT_MAIL_FROM'])
    ->to($_ENV['ERROR_REPORT_MAIL_TO'])
    ->subject($subject)
    ->text($text_content);

try {
    $mailer->send($email);
} catch (TransportExceptionInterface $e) {
    echo 'Caught exception: ' . $e->getMessage() . "\n";
}
この記事を書いた人
グッドネイバー

“ Webに悩むお客さまの「よき隣人」でありたい ” をモットーに、Web システム開発(主に Laravel)、Web マーケティング支援の仕事をしています。お仕事のご依頼・ご相談はこちらからお気軽にどうぞ。