MUGIJIRU.JP

Webエンジニアの雑談ブログ

(PHP4.0.6以上) 文字を特定のbyte数以内に収める

全角・半角文字の混在した文字列を特定のbyte数に収めるための便利な関数の紹介です。

公式Doc

PHP: mb_strcut - Manual

先頭から32byte以内で切り捨てる、みたいな処理を入れるなら下記のような感じになります。

<?php
$trimmed = mb_strcut($string, 0, 32);

ちなみに

色々やっているシステムに導入する場合は文字コードにも気を遣う必要があります。
たとえば、物流システムの出荷データcsvをシステムで作成する場面などが考えられます。
最終的にSHIFT-JISで利用されるデータで、作成するシステムはUTF-8だとするならば
文字コードによってbyte数は異なりますから、変換前にmb_structしても正しく収まりません。
最終的に利用される文字コード上での処理をしなければ
過剰/不十分な切り詰めになってしまうといったケースが考えられるからです。

上記関数を使うならば、とりあえずmb_convert_encoding等で取り扱いたい文字コードに変換すると良いと思います。
(文字コードの仕組みを熟知している人ならば色々と頭のいい変換方法があるかもしれませんがここでは触れません)

サンプル関数
<?php
/**
 * 特定のbyteに収まるように文字列を切り出す
 * mb_convert_encodingはそこそこリソースを使うのでデータ量に注意
 *
 * @param string  $str      文字列
 * @param integer $byte     収めたいバイト数
 * @param string  $encoding 最終的に出力する文字コード 
 * @param integer $start    開始位置
 *
 * @return trimされた文字列
 */
function mb_truncate_by_bytenum($str, $byte, $encoding = null, $start = 0)
{
    if ($encoding) {
        $str = mb_convert_encoding($str, $encoding);
    }

    $trimmed = mb_strcut($str, $start, $byte, $encoding ? $encoding : mb_internal_encoding());

    if ($encoding) {
        $trimmed = mb_convert_encoding($trimmed, mb_internal_encoding(), $encoding);
    }

    return $trimmed;
}

Laravel5.2 RouterとRoute

ルーティングのパラメータの取り方を時々忘れてしまいますので、
備忘録としてたまに使うものを記載しておこうと思います。

前提知識

RouterとRoute

Router

Routerクラスのファサードは「Route」と命名されています。
お馴染みのroutes.phpで利用している「Route:: ***」の実体で、ざっくり言えば経路全体の管理を行うオブジェクトです。
Routerのソースは「Illuminate\Routing\Router.php」です。
この「Route」というファサードの命名が曲者で、Routerの子として扱われる「Route」クラスが別にあるので
混ざってややこしい
です(言ってるそばからややこしいですね)

Route

経路そのものを表現するオブジェクトです。
ソースは「Illuminate\Routing\Route.php」です。

それぞれ、いろんなメソッドがあるので覗いてみると面白いです。

名前付きルーティングを活用しているとき忘れがちな諸々

Router, Routeオブジェクトでたまに使う

<?php
// Routerから現在の名前を取る
$route_name = Route::getCurrentRouteName();
// Routeインスタンスから直接名前を取る
$route_name = $route->getName();

// Routerで今表示しているページのルーティングが「'mypage.dashboard'」か判定する
$bool = Route::is('mypage.dashboard');
// ワイルドカードも使える
$bool = Route::is('mypage.*'); 

// Routeインスタンスからルーティングパラメータを取る(RouteModelBindingしている場合はインスタンスで取れる)
$param = $route->paremeter('hoge');
$param = $route->getParameter('hoge'); // 上記のエイリアス
// パラメータを持っているかどうかも確認できる
$bool = $route->hasParameter('hoge'); // hogeを受けるようになっているか
$bool = $route->hasParameters(); // なんらかパラメータを受けるようになっているか

現在のリクエストのRouteの取り方

<?php
/**
 * 下記は全部同じインスタンスを取ります
 * 
 */
// Routerのファサードから
$route = \Route::current();
$route = \Route::getCurrentRoute(); // 上記メソッドのエイリアス
// Requestのファサードから
$route = \Request::route();
// controller等で自動解決されたrequestインスタンスから
$route = $request->route();

/**
 * (補足)ちなみにRouterからRequestインスタンスを取る事もできます
 *
 */
$request = Route::getCurrentRequest();

単語で捉えているとファサードの命名のせいで混乱するので、Routerに用があるのか、Routeに用があるのかを認識したうえで
問い合わせしていくことが大切ですね。

Laravel5.2のメール送信で複数の設定を使い分ける

Laravel5.2のメール送信でよく使われるのはMail::sendだと思っていますが、
特定の処理だけ別のsmtpサーバを介して送信をしたいときの設定方法がなかなか見つからなかったので
自分の試した実装を紹介します。

業務システム等では、たまに特定のアドレスに対してのみ別のsmtpサーバを介してメールを送信する要件があったりするわけですが、どこに手を入れたら良いものかとずいぶん悩みました。

まずLaravelで一般的なMail::send

ドキュメント等でも書かれている例ですが、単純にメールを送るだけならbladeテンプレートを利用して簡単に実装できます。

送信サーバ等の設定

.envで行います。

MAIL_DRIVER=sendmail
MAIL_HOST=localhost
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
実装
<?php
$view_script_path = 'mail.thanks';
$from = 'hoge@example.com';
$from_name = 'test送信者';
$subject = 'メールタイトル';

Mail::send(
    ['text' => $view_script_path], // テキストメール
    $data,
    function ($message) use ($from, $from_name, $to, $subject) {
        $message
            ->from($from, $from_name)
            ->to($to)
            ->subject($subject);
    }
);

設定を上書きするには?

ぱっと見、「ファサードかこの$messageでサーバ設定をオーバーライドできる関数が呼べるんだろうか。」と思うんですが、無さそうなんですよね。

ソースを一通り解析してみると、サーバ等の設定はServiceProviderのregisterの時点で固定されているようで、config::setでの変更もできないことがわかりました。
Transport設定の作り分けも相当奥までconfigが入り込んでいて、どうやらLaravel5.2のメール送信は一つの設定で実施するのが前提の思想でできているようです。

色々考えた結果

泥臭いですが、ServiceProviderとSwift_MailerのTransportを生成しているTransportManagerの仕事を自力でやるSwift_Mailerの入替えをやるのがまずは妥当かなと思い、関数を書いてみました。

※5/30 18:45 更新 (当記事を見た職場の方から、setSwiftMailerメソッドのアドバイスを頂いたお陰で内容が改善されました)

<?php
/**
 * メール送信関数
 *
 * Laravel5.2のMail::send のtransportの融通を利かせたバージョン
 *
 * sendmail, smtp以外に対応する場合はIlluminate\Mail\TransportManagerを参考に追加実装すること
 *
 * @param mixed    $view     Mail::sendの第1引数 ['text' => {viewスクリプトパス}] 等
 * @param array    $data     Mail::sendの第2引数 
 * @param callable $callback Mail::sendの第3引数
 * @param array    $override_config 特定の設定で送信する場合のみ指定  config('mail')を上書きするための情報
 * @param bool     $perpetuate      設定を永続化させるか true:永続化させる
 * 
 */
function send_mail($view, array $data, $callback, array $override_config = [], $perpetuate = false)
{
    if (!$override_config) {
        return \Mail::send($view, $data, $callback);
    }

    // Swift_Mailerの入替え処理クロージャ
    $replace_swift_mailer = function($config) {
        app()['config']['mail'] = $config;
        \Mail::setSwiftMailer(new Swift_Mailer((new \Illuminate\Mail\TransportManager(app()))->driver()));
    };

    // 現在のconfig
    $current_config = config('mail');

    // 新しいconfigを設定
    $replace_swift_mailer(array_merge($current_config, $override_config));

    // 送信
    $result = \Mail::send($view, $data, $callback);

    // 永続化しない場合は設定を元に戻す
    if (!$perpetuate) {
        $replace_swift_mailer($current_config);
    }

    return $result;
}

この関数の第4引数に下記のような配列を渡してやれば、その設定で送信ができるようになります。

<?php
// gmailで送る例
$custom_config = [
    'driver'     => 'smtp',
    'host'       => 'smtp.gmail.com',
    'port'       => 587,
    'username'   => 'hogehoge@gmail.com',
    'password'   => 'xxxxxxxx',
    'encryption' => 'tls'
];

Laravel5.3はMailableインタフェースが追加になっているわけですが、今回の要件にどうすれば対応できるのかも気になる所です。

はてなブログはじめました

会社の関係でエンジニアブログを毎月投稿していたのですが、今後ははてなブログで継続して行こうと思います。
旧ブログは下記です。
shirangana.omaww.net