MUGIJIRU.JP

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

composerでPHPのバージョンを指定する

しょうもない話なのですが・・・

開発端末A: PHP 7.0.1
開発端末B: PHP 7.0.11
開発端末C: PHP 7.1.8

で共通のブランチに対して同時にプロジェクトの開発作業をしている状況で、
開発端末BのPHP 7.0.11でcomposer updateを実施し、composer.lockを更新しました。
すると、一部のライブラリにPHP 7.0.8以上を前提とするアップデートが入ってしまい、
下記のように開発端末AのPHP7.0.1でcomposer installが不能になるという事態になってしまいました。

Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for symfony/event-dispatcher v3.3.8 -> satisfiable by symfony/event-dispatcher[v3.3.8].
    - symfony/event-dispatcher v3.3.8 requires php ^5.5.9|>=7.0.8 -> your PHP version (7.0.1) does not satisfy that requirement.
  Problem 2
    - Installation request for symfony/yaml v3.3.8 -> satisfiable by symfony/yaml[v3.3.8].
    - symfony/yaml v3.3.8 requires php ^5.5.9|>=7.0.8 -> your PHP version (7.0.1) does not satisfy that requirement.
  Problem 3
    - symfony/event-dispatcher v3.3.8 requires php ^5.5.9|>=7.0.8 -> your PHP version (7.0.1) does not satisfy that requirement.
    - symfony/http-kernel v3.0.9 requires symfony/event-dispatcher ~2.8|~3.0 -> satisfiable by symfony/event-dispatcher[v3.3.8].
    - Installation request for symfony/http-kernel v3.0.9 -> satisfiable by symfony/http-kernel[v3.0.9].

対応

プロジェクトの都合上、当ブランチではPHP 7.0.1での動作を保証する必要があったため、実施した対策としては
composer.jsonに下記指定をして、composer.lockを一度削除してcomposer installを行いました。

...
    "config": {
...
        "platform": {
            "php": "7.0.1"
        }
    }
}

これで生成されたcomposer.lockを利用してcomposer installを実施したところ
全ての端末で正常に依存関係の解決が行われました。一安心。

PHPのバージョンが上の端末ではしっかりダウングレードが行われていました。

Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 0 installs, 2 updates, 0 removals
  - Updating symfony/event-dispatcher (v3.3.8 => v3.3.6): Downloading (100%)         
  - Updating symfony/yaml (v3.3.8 => v3.3.6): Downloading (100%)         
Generating autoload files
> Illuminate\Foundation\ComposerScripts::postInstall
> php artisan optimize

よくできてるなーと思います。

人に言われて心に残っている言葉 4選

最近顧客折衝とか設計ばっかりでコードを書けておらず、技術的なネタがないので、
表題の通りですが「人に言われて心に残っている言葉」をぱっと浮かぶものを少しだけ挙げます。

1.「俺は人生のベテランかもしれないが父親としては初心者だった」

〜 学生時代 / 家族の思い出話をしている最中の父親より
息子があくまで自分とは違う人間である事をしっかり分別できていなかった、という言葉と共にこの言葉を聞きました。
人に何かを伝えたり協力したり育てたりする上で必ず念頭に置くべきポイントだと今でもたまに言います。

2.「技術者として食っていきたいなら聞きやすく聞かれやすい人間関係を作るといい」

〜 学生時代 / 元MicrosoftWindowsの開発をしていたらしい非常勤講師の先生より
技術力向上のためのアドバイスを求めて授業後に職員室に張り付いて質問した時の返答。
もっと具体的な勉強方法等を期待していたので、なんでここで会社の人との話?と思ったものです。
今ならよくわかります。

3.「データベースの設計がしっかりできているかが重要なんだよね」

〜 プログラミング初心者時代 / 自分を鍛えてくれた上司より
キツい仕様変更を元々の設計のお陰でスムーズに乗り切れた時に聞いた言葉です。
威力を目の当たりにしたので心に刻まれました。
いまはそれがシステム開発の本質の一端なのだとしっかり感じています。

4.「"すごいをうれしいに"っていうの、どうよ」

〜 プログラミングに慣れた頃 / 現職社長より
会社のメインテーマが爆誕した瞬間のセリフです。
現在も自分のエンジニアとしての姿勢を決める大切な指針になっています。


以上です。
念頭に置いておくと普段の行動が良い方向に変化する言葉は偉大だと思いますね。

(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