- 投稿日:2019-12-05T23:33:28+09:00
Laravelで画像サイズを変更して保存する方法がめちゃめちゃ簡単だった話
はじめまして!qiita初投稿です?
最近Laravelで開発することが増えてきて、その中でも便利だなーと思った「画像サイズの圧縮」について書きます。
随時間違っている部分などは訂正していくと思います。ご指摘もいただけると嬉しいです?
開発環境
PHP 7.3.11
Laravel Framework 6.5.1
Composer version 1.9.1Laravelで画像処理をする。
今回自分は「interventionImage」というパッケージを利用して、画像サイズを小さくして保存を実装しました。
interventionImageはphpの画像処理ライブラリGD LibraryとImagickをサポートしています。
自分はGD Libraryを使用して実装しました。
GD Libraryのインストール方法は環境によっても変わってくると思うので、お使いの環境に合わせてインストールしてください。
実装までの流れ
interventionImageの公式はこちら
http://image.intervention.io/getting_started/installation#laravel実装の流れとしては、
1.パッケージのインストール。
2.Laravelで使用できるようにセットアップ。
3.実際に画像サイズを変更してみる。1.Laravelにintervention/imageをインストールする。
composerを使ってintervention/imageをインストール。
composer require intervention/image
2.セットアップ
InterventionImageをインストールしたら、Laravel構成ファイル
config/app.php
を開き、providers配列とaliases配列に次の行を追加します。providers配列に、このパッケージのサービスプロバイダーを追加。
Intervention\Image\ImageServiceProvider::class,
このパッケージのファサードをaliases配列に追加。Imageだとモデルとして定義したクラスと被りそうだったので、InterventionImageとしてaliasを保存。
'InterventionImage' => Intervention\Image\Facades\Image::class,
config/app.php~前略~ 'providers' => [ ~中略~ // Add Intervention Image. Intervention\Image\ImageServiceProvider::class, ], 'aliases' => [ ~中略~ 'Hash' => Illuminate\Support\Facades\Hash::class, // Add Intervention Image. 'InterventionImage' => Intervention\Image\Facades\Image::class, ~後略~3.実際に画像サイズを変更してみる。
今回は画像のアスペクト比をそのままで保存しつつ、横幅を1080pxにしました。
config/PostsController.php//エイリアスに追加したファサードを呼び出す。 use \InterventionImage; ~中略~ //formからの画像リクエストはimagefileで受け付けしたのでimagefileを設定しています。formに合わせて変更してください。 $file = $request->file('imagefile'); $name = $file->getClientOriginalName(); //アスペクト比を維持、画像サイズを横幅1080pxにして保存する。 InterventionImage::make($file)->resize(1080, null, function ($constraint) {$constraint->aspectRatio();})->save(public_path('/images/' . $filename ) );;ちなみに、横幅と高さを指定する場合はもう少しシンプルにできます。
InterventionImage::make($file)->resize(1080, 700)->save(public_path('/images/' . $filename ) );;
resize
で画像の大きさを指定するだけだったので、めっちゃ簡単だなーと思いめっちゃ感動しました。まとめ
少し面倒だと思ってた画像のresizeが、かなりシンプルな書き方で実装できて感動しました?
とはいえqiitaの初投稿もできたので、これきっかけでqiitaへの投稿を増やして行けたらいいなーと思いました!
- 投稿日:2019-12-05T22:57:26+09:00
今後のShizuoka.phpについて
これは、静岡 Advent Calendar 2019の5日目の記事となります。
静岡でPHPの勉強会なかったので、今年からShizuoka.phpをはじめました。
JavaScript, Python, Go, Haskell, Rubyの勉強会は開催されているのにPHPがない・・・。
そんな危機感をもってから5年ぐらい経って、やっと今年#1を開催できました?定期的に続けて行きたい
3 ~ 4ヶ月に一度開催できればいいのかなと。
頻度そんなに多くはできませんが、定期的に開催していこうと思っています。
だから来てね!ロゴできたよ!
refs: https://twitter.com/planpot/status/1201411166162931712?s=20
@planpot先生にロゴを作ってもらいました?
爆速で作っててマジで感動しました!!!!静岡で勉強会を主催する事について
僕は今東京に住んでいます。
みんな勘違いされますが、静岡には住んでいません。脱藩しました。
僕は東京に住んでいます。大事なのでもう一度言います、東京に住んでいます。
(まあ毎週のように静岡に行くのでアレですが・・・w)そんな僕が地元で勉強会を主催するのは2つの理由があります。
つながりの場を提供したい
静岡JavaScript勉強会 #1に参加して、地元静岡のエンジニアと知り合いになれました。
あれから8年も立つにに未だに交流がある人がたくさんいます。
やっぱそういうつながりがあると、嬉しいし、楽しいし、技術的に学べることもたくさんあります。
あとは勝手に勉強会作られたりとかwwww同じようにShizuoka.phpで出来たらなと思っています。
地元に帰るという選択肢を持てる
勉強会に参加してみると、意外とIT企業があるんだということに驚かされました。
8年ほど前僕が求人情報で探したら、ぜんぜん見つからなかったのに・・・。勉強会に参加することで、地元のIT企業を知れるというメリットもあり、地元に帰りたいエンジニアの手助けになればと。
地元が好き
静岡のエンジニアの雰囲気というか、やはり同じ地元の人と話すの楽しいし、地元の人達とPHPの話するのが楽しいです。
静岡最高です!!!!!これからの取組みについて
当日LT枠を用意します。
勉強会でよくある「あーやっぱLT枠申し込めばよかったなあー」っていう人いますよね?
そんな人の為に宣伝枠(当日LT枠)を用意しようと考えています。ちょっとした発表でいいので少しでもみんなに喋ってもらい、発表の敷居を少しでも下げたいという気持ちや、当日LTしたくなっちゃう気持ち。
そんな人のために枠を設けようと思っています。スタッフの皆さんありがとうございます。
僕が勝手にスタッフと言っている @tomofさん, @toniokatanuki先輩には本当に感謝しきれないぐらい感謝しています。
僕はイベント運営が苦手な人間なので、かなり支えてもらっています。ありがとうございます。最高です。いずれは主催者を変えたい
本来は東京に住んでいる僕が主催者をやるべきではないと考えています。
今は僕が主催者としてがんばりますが、そのうち誰かに引き継げたらと思っています。次回開催予告
次回#2は2/8開催予定です。
場所は三島になります。
細かいことが決まり次第connpass立てます。最後に
静岡は東京からも近いし、気候も良いし、自然も多いし、富士山は静岡のものだし、本当に良いところです。
そんな静岡でもっとITが盛り上がったらいいのにと常日頃から思っています。
すこしでも力になれればと思い、これからも継続てShizuoka.phpを開催していければと思います。最後の最後に
ちょっとでもShizuoka.phpが気になった方は気軽に参加してください!
- 投稿日:2019-12-05T22:17:11+09:00
WordPress 学習メモ 第1回
急遽WordPressを触らないといけない事になりキャッチアップのフローを、ノート代わりにまとめていきます。
主に自分の学習の流れを振り返りで残す形なので色々、省いてます。
Webエンジニアの諸先輩方からアドバイスやご指摘を頂けたらありがたいです!学習を始める前に
今回の目的は環境構築を行う事です。
既に存在する本番環境と同じ物をローカル環境に構築するのが、最初のスタートでした。
そもそもどうやって環境構築しようかなと思ってた際に、参考にさせて頂いたサイトです。
これがめちゃくちゃ簡単に環境を構築できたので驚きました。超簡単にローカル環境が構築できるLocalbyFlywheelの使い方
本番サイトと同環境を構築する方法!
さて、環境構築が完了し、本番と状態にする際の手順を紹介します。
たった3ステップでサイトの引っ越しが完了するプラグイン「All-in-One WP Migration」が簡単すぎ!1:作成したローカル環境にプラグインである、All-in-One WP Migrationをダウンロードします。
2:本番のAll-in-One WP Migrationエクスポートしファイルをダウンロードする。
3:ローカル環境でさっきと逆の手順でAll-in-One WP Migrationのインポートファイルを先ほどダウンロードしたファイルを選択します。
環境構築完了!
これで開発をスタートできます。
次からは、実際に開発を行う際の手順を説明させて頂きます!
- 投稿日:2019-12-05T22:10:31+09:00
PHPで高速・省メモリ・確実に日本語CSVを扱う方法
結論
composer require fw3/streams
としてfw3/streamsを使えば、日本語CSV取り扱いにおける労苦から解放されるよ。- ラッパーメソッドもいいけど、\fgetcsv関数、\fputcsv関数、\SplFileObjectを使ってもシンプルに実現できるかもしれないよ。しかもより高速に動作する。
- 取り急ぎ"PHPで高速・省メモリ・確実に日本語CSVを扱いたい"場合は使い方へどうぞ。
必要なもの
PHP 7.2.0以降のPHP + ext-mbstring + fw3/streams
または
PHP 5.3.3以降のPHP + ext-mbstring + みんなのPHP 現場で役立つ最新ノウハウ!のChapter 7.3 サポートコード
お断り
- この記事においてShift_JISとはWindows-31Jを指します。
- この記事において入力元・出力先のCSVファイルは文字セット:Shift_JIS、改行コード:CRLFとして扱います。
- この記事において入力先・出力元のデータは文字セット:UTF-8として扱います。
この記事はどういうお話なの?
様々なCSV入出力方法の課題を挙げ、それらの課題に対して一括で対処できるライブラリの紹介と使い方についてお話します。
既知の日本語CSV読み書き時の課題
既存の日本語CSV読み書きには次の課題がありました。
\fgetcsv関数、\fputcsv関数、\SplFileObject::fgetcsv、\SplFileObject::fputcsvだと良くわからないタイミングで文字化けが発生する
PHP標準関数、標準クラスとして用意されている\fgetcsv関数、\fputcsv関数、\SplFileObject::fgetcsv、\SplFileObject::fputcsv。
ASCIIのみで構成されたCSVを扱うには必要十分でしたが、日本語での文字セットを持つCSVファイルを扱う場合、文字化けが発生したり意図しない箇所で解析が終わるなど、問題のある場合がありました。具体的には\fgetcsv関数、\fputcsv関数、\SplFileObject::fgetcsv、\SplFileObject::fputcsvはOS設定であるロカールの影響を受け、ロカールの文字セットと読み書きするCSVの文字セットが一致していない場合、文字化けが発生します。
ロカールの設定自体はPHPでも\setlocaleで実行時に任意に変更することが出来るのですが、同一リクエスト中の処理全体に影響を与えることと、期待するロカールがインストールされている保証がないため、大変扱いにくい状況にありました。
<?php // 入力 $locale = \setlocale(LC_ALL, '0'); \setlocale(LC_ALL, 'Japanese_Japan.932'); // もしも'Japanese_Japan.932'がインストールされていれば。期待薄。なければ正常に動作しない。 $fp = \fopen($path_to_csv, 'rb'); for (;($row = \fgetcsv($fp, 1024)) !== FALSE;) { \mb_convert_variables('UTF-8', 'SJIS-win', $row); $rows[] = $row; } \fclose($fp); \setlocale(LC_ALL, $locale); // または $locale = \setlocale(LC_ALL, '0'); \setlocale(LC_ALL, 'Japanese_Japan.932'); // もしも'Japanese_Japan.932'がインストールされていれば。期待薄。なければ正常に動作しない。 $fileObject = new \SplFileObject($path_to_csv, 'rb') $fileObject->setFlags(\SplFileObject::READ_CSV); foreacho ($fileObject as $row) { \mb_convert_variables('UTF-8', 'SJIS-win', $row); $rows[] = $row; } \setlocale(LC_ALL, $locale);<?php // 出力 $fp = \fopen($path_to_csv, 'wb'); foreacho ($rows as $row) { \mb_convert_variables('SJIS-win', 'UTF-8', $row); \fputcsv($fp, $row); } \fclose($fp); // または $fileObject = new \SplFileObject($path_to_csv, 'wb') foreacho ($rows as $row) { \mb_convert_variables('SJIS-win', 'UTF-8', $row); $fileObject->fputcsv($row); }\file_get_contents関数、\file_put_contents関数で実装するとプロセスあたりのメモリが足りなくなる
ロカールに頼らず文字セットを変更しようとする場合、\mb_convert_encoding関数を用いることが定石です。
この場合、出力時は逐次変換を行う選択も取れるのですが、入力時はどこまでが文字セット変換の対象文字列かがわからないため、一度すべてを変数に乗せる必要がありました。
そのため、対象となるファイルが巨大な場合、メモリーリミットに到達しfatalエラーを誘発し、UXが最悪となる、いわゆる白画面が表示されることとなりました。
<?php // 入力 $csv_string = \mb_convert_encoding(\file_get_contents($path_to_csv), 'UTF-8', 'SJIS-win'); $locale = \setlocale(LC_ALL, '0'); \setlocale(LC_ALL, 'ja_JP.utf8'); // もしも'ja_JP.utf8'がインストールされていれば。なければ正常に動作しない。 $fp = \fopen('php://memory/maxmemory:100', 'wb+'); \fwrite($fp, $csv_string); \rewind(0); for (;($row = \fgetcsv($fp, 1024)) !== FALSE;) { $rows[] = $row; } \fclose($fp); \setlocale(LC_ALL, $locale);一時ファイルを使用するとシステム全体の実行効率が悪化する
「\fopen関数 + \fread関数で読み込めたところまでで文字セット変換し、そのままファイルに出力する」という手段が用いられることもあります。
ただしこれは大量のファイルI/Oが発生するため、実行速度が低下したり、システム全体の負荷として他のリクエストや処理にまで悪影響を与えます。
また、一時ファイルの出力先や一時ファイルそのものなど、関心ごとの外まで管理しなければならないため、使い勝手がよくありませんでした。<?php // 入力 $locale = \setlocale(LC_ALL, '0'); \setlocale(LC_ALL, 'Japanese_Japan.932'); // もしも'Japanese_Japan.932'がインストールされていれば。期待薄。なければ正常に動作しない。 $fp = \fopen($path_to_csv, 'rb'); $temp_fp = \fopen('php://temp', 'wb+'); for (;($row = \fgets($fp, 1024)) !== FALSE;) { \fwrite($temp_fp, \mb_convert_encoding('UTF-8', 'SJIS-win', $row)); } \rewind(0); for (;($row = \fgetcsv($temp_fp, 1024)) !== FALSE;) { $rows[] = $row; } \fclose($temp_fp); \fclose($fp); \setlocale(LC_ALL, $locale);\popen関数で実装すると物理メモリが足りなくなっても気づけない
PHPで600MBほどのSJISのCSVを読み込んでUTF-8として処理する場合のサンプルコード - Qiitaにサンプルコードがありますが、\popen関数を用いて現在のプロセスとは別のプロセスでファイル全体に対して一括で文字セット変換を行う手段もあります。
ですが、これは意図して設定していたはずのmemory_limitを解除して動作させてしまうため、誰も意図していないサイズのメモリを確保される恐れが高いです。
また、\popen関数で開いたプロセスを正しく閉じなかった場合に意図しない状況が発生する恐れが高いです。iconvストリームフィルタを使用すると文字セット変換に失敗した行が消える
iconvストリームフィルタを使うことで透過的に文字セット変換を行うことができます。
ですが、iconvストリームフィルタでは変換できない文字を含んだ行は0バイトとして扱われ削除されます。
出力されたCSV上では正しく1行が消えただけにしか見えません。
そのため、運用中に不具合があったことに気づくことが難しいです。また、変換できない文字が存在した場合、強制的にNoticeが表示され、これを阻止する事は出来ません。
そのため、開発環境モードのLaravelなど、Noticeをトラップして例外としてスローする環境では必ずそこで処理が停止するため、作業の手が止まることになります。<?php // 入力 $fp = \fopen(\sprintf('php://filter/read=convert.iconv.Windows-31J%2FUTF-8/resource=%s', $path_to_csv), 'rb'); for (;($row = \fgetcsv($fp, 1024)) !== FALSE;) { $rows[] = $row; } \fclose($fp);<?php // 出力 $fp = \fopen(\sprintf('php://filter/write=convert.iconv.UTF-8%2FWindows-31J/resource=%s', $path_to_csv), 'rb'); foreacho ($rows as $row) { \mb_convert_variables('SJIS-win', 'UTF-8', $row); \fputcsv($fp, $row); } \fclose($fp);既知の日本語CSV読み書き時の課題のまとめ
- \fgetcsv関数、\fputcsv関数、\SplFileObject::fgetcsv、\SplFileObject::fputcsvだと良くわからないタイミングで文字化けが発生する
- \file_get_contents関数、\file_put_contents関数で実装するとプロセスあたりのメモリが足りなくなる
- 一時ファイルを使用するとシステム全体の実行効率が悪化する
- \popen関数で実装すると物理メモリが足りなくなっても気づけない
- iconvストリームフィルタを使用すると文字セット変換に失敗した行が消える
以上が既知の日本語CSV読み書き時の課題となります。
これらを問題単位で切り分けると次になります。
- 正しいロカールがインストールされていないとCSVとして正しく読み込めない
- 実行時に過大なメモリを消費する
- 一時ファイルで回避した場合、システム全体のリソースを圧迫する
課題解決:このライブラリを使うことで得られる効果や副次的な効果
このライブラリを使うことで得られる効果
次の通り、既知の課題を全て解決できます。
- ロカールのインストール状況問わず、正しくCSVとして読み込める
- 行単位でメモリ展開、文字セット変換を行うため省メモリ
- \fopen関数や\SplFileObject使用箇所などの既存実装に組み込みやすい
- PHP nativeに近い箇所で動作するため、PHP標準関数の支援を受けやすい
- Tempファイルなどの一時出力先が不要
副次的な効果
また、ライブラリ実装者の知見が反映されているため、次の副次的な効果を得られます。
- エンコード自動判定時に正確な判定が期待できる
- 変換できない文字があった場合のふるまいを決められる
- 意図した行末改行文字で出力を行える
- \fputcsv関数はシステムのOSに依存して出力時の改行コードが変わります
- Specクラス経由で設定を注入することもできるため、直感的かつ確実に、経験が薄くても高速に設定を構築できます
使い方
単純に使うだけならば、次の3パターンのみで問題ありません。
デリミタやエンクロージャ、エスケープを変更したい場合は\fgetcsv関数、\fputcsv関数の引数や、\SplFileObject::setCsvControlによる設定を使用してください。
読み込み
<?php use fw3\streams\filters\ConvertEncodingFilter; use fw3\streams\filters\utilitys\StreamFilterSpec; use fw3\streams\filters\utilitys\specs\StreamFilterConvertEncodingSpec; // CSVファイルのパスを設定して下さい $path_to_csv = ''; // フィルタ登録 StreamFilterSpec::registerConvertEncodingFilter(); // デリミタ、エンクロージャ、エスケープの設定 $delimiter = ','; // TSVにしたい場合にはここにタブを設定する $enclosure = '"'; $escape = "\\"; // フィルタの設定 $spec = StreamFilterSpec::resource($path_to_csv)->read([ StreamFilterConvertEncodingSpec::toUtf8()->fromSjisWin(), ]); // \fopenでの実装 // Shift_JIS => UTF-8としてCSV読み込みを行う $rows = []; $fp = \fopen($spec->build(), 'rb'); for (;($row = \fgetcsv($fp, 1024, $delimiter, $enclosure, $escape)) !== FALSE;) { $rows[] = $row; } \fclose($fp); // \SplFileObjectでの実装 // Shift_JIS => UTF-8としてCSV読み込みを行う $rows = []; $fileObject = \SplFileObject($spec->build(), 'rb'); $fileObject->setFlags(\SplFileObject::READ_CSV); $fileObject->setCsvControl($delimiter, $enclosure, $escape); foreach ($fileObject as $row) { $rows[] = $row; }書き込み
<?php use fw3\streams\filters\ConvertEncodingFilter; use fw3\streams\filters\ConvertLinefeedFilter; use fw3\streams\filters\utilitys\StreamFilterSpec; use fw3\streams\filters\utilitys\specs\StreamFilterConvertEncodingSpec; use fw3\streams\filters\utilitys\specs\StreamFilterConvertLinefeedSpec; // CSVファイルのパスを設定して下さい $path_to_csv = ''; // フィルタ登録 StreamFilterSpec::registerConvertEncodingFilter(); StreamFilterSpec::registerConvertLinefeedFilter(); // デリミタ、エンクロージャ、エスケープの設定 $delimiter = ','; // TSVにしたい場合にはここにタブを設定する $enclosure = '"'; $escape = "\\"; // フィルタの設定 $spec = StreamFilterSpec::resource($path_to_csv)->write([ StreamFilterConvertEncodingSpec::toSjisWin()->fromUtf8(), StreamFilterConvertLinefeedSpec::toCrLf()->fromAll(), ]); // \fopenでの実装 // UTF-8 => Shift_JISとしてCSV書き込みを行う $fp = \fopen($spec->build(), 'wb'); foreach ($rows as $row) { \fputcsv($fp, $row); } \fclose($fp); // \SplFileObjectでの実装 // UTF-8 => Shift_JISとしてCSV書き込みを行う $fileObject = \SplFileObject($spec->build(), 'wb'); $fileObject->setCsvControl($delimiter, $enclosure, $escape); foreach ($rows as $row) { $fileObject->fputcsv($row); }直接ダウンロードさせる場合
<?php use fw3\streams\filters\ConvertEncodingFilter; use fw3\streams\filters\ConvertLinefeedFilter; use fw3\streams\filters\utilitys\StreamFilterSpec; use fw3\streams\filters\utilitys\specs\StreamFilterConvertEncodingSpec; use fw3\streams\filters\utilitys\specs\StreamFilterConvertLinefeedSpec; // CSVファイルのパスを設定して下さい $path_to_csv = ''; // フィルタ登録 StreamFilterSpec::registerConvertEncodingFilter(); StreamFilterSpec::registerConvertLinefeedFilter(); // デリミタ、エンクロージャ、エスケープの設定 $delimiter = ','; // TSVにしたい場合にはここにタブを設定する $enclosure = '"'; $escape = "\\"; // フィルタの設定 $spec = StreamFilterSpec::resourceOutput()->write([ StreamFilterConvertEncodingSpec::toSjisWin()->fromUtf8(), StreamFilterConvertLinefeedSpec::toCrLf()->fromAll(), ]); // DL用のヘッダ:簡易版 \header('Content-Type: application/octet-stream'); \header('Content-Disposition: attachment; filename=sample.csv'); // \fopenでの実装 // UTF-8 => Shift_JISとしてCSV書き込みを行う $fp = \fopen($spec->build(), 'wb'); foreach ($rows as $row) { \fputcsv($fp, $row); } \fclose($fp);使用上の注意点
[必須] 日本語CSVの読み込みを行う場合はConvertEncodingFilter::startChangeLocale()を実行してください
適切なロカールが設定できていないと、\fgetcsv関数、\SplFileObject::fgetcsvによるCSV読み込み時に意図しない動作となります。
[必須] フィルタの使用を終えた場合はConvertEncodingFilter::endChangeLocale()を実行してください
ConvertEncodingFilter::endChangeLocale()を実行することで、ConvertEncodingFilter::startChangeLocale()を実行する前の状態に戻すことができます。
ロカールの設定は同一プロセス中(HTTPアクセスの場合は一般的に一リクエスト中)維持されます。
その為、ロカールの設定を元に戻さないと、意図しない箇所で意図しないロカールでの処理が実行されるリスクがあります。[必須] フィルタを使う前にStreamFilterSpec::registerConvertEncodingFilter()、StreamFilterSpec::registerConvertLinefeedFilter()を使用しフィルタの登録を行ってください
フィルタが登録されていない場合、当然のことながらフィルタを使用する事はできません。
StreamFilterSpec::registerConvertEncodingFilter()、StreamFilterSpec::registerConvertLinefeedFilter()を使用してストリームフィルタを設定してください。
- 投稿日:2019-12-05T22:06:27+09:00
PHPにてエラーが起き、画面が真っ白になった時の対処(エラー表示の仕方)
PHPを使ってカレンダー機能を実装したいので
枠組みを作り、あとは中だけ。
requireにて外部ファイルと組み合わせてみると、さっきまで表示されていた枠組みまでもが消えてしまった。
きっと中のコードがエラーを起こしたことが原因だなと思ったので
まずはエラーが起きてしまっている場所を特定する必要があるので「PHP エラー 表示」で検索。
PHPにてエラーの場所を特定するには
<?php ini_set( 'display_errors', 1 );?>をコードの一番上に書けばphp.iniでエラー表示設定することなくエラー場所を表示できるらしい。
ちなみに、出てきたエラー表示は
Parse error: syntax error, unexpected 'mktime' (T_STRING), expecting ')' in /Applications/MAMP/htdocs/calender_index.php on line 13と表示されたので13行目をチェック。
なんと、「,」が抜けていたことが原因でした...
またスペルミスか〜〜。
修正し、無事カレンダーは表示!
スペルミスは怖いものです。
- 投稿日:2019-12-05T21:31:42+09:00
【Wordpress】投稿ページ(single.php)において、複数ページ(複数テンプレート)に出し分ける方法
実現したかったこと
ひとつの投稿において、情報によってページを切り替えられるようにしたい
投稿が一つのお店のページになっていて、さらにタブでメニューやクーポンをぶら下げるような作りに。
要は食べログやぐるなびのイメージです。
各ページの出しわけは、URLにパラメーターを追加することで判定します。投稿(美味しい餃子 渋谷店)のトップURL
https://hogehoge.com/cat/美味しい餃子渋谷店/投稿(美味しい餃子 渋谷店)のメニューURL
https://hogehoge.com/cat/美味しい餃子渋谷店/?info=menu投稿(美味しい餃子 渋谷店)のアクセスURL
https://hogehoge.com/cat/美味しい餃子渋谷店/?info=accessやること
1. テンプレの用意
トップ用のテンプレとは別に、menu用、access用のテンプレを用意します。
single.php
single-menu.php
single-access.php2. function.phpにフィルターフックを追加
single_templateに対してフィルターフックを追加します。
以下、ソースの例です。function.phpfunction shop_template_switch($template) { $new_template = $template; if (isset($_GET['info'])) { $new_template = 'single-' . esc_html($_GET['info']) . '.php'; if (is_array($template)) { $new_template = array( $new_template, isset($template[1]) ? $template[1] : 'single.php' ); } else { $new_template = preg_replace('/[^\/]+\.php$/i', $new_template, $template); if (!file_exists($new_template)) { $new_template = $template; } } } return $new_template; } add_filter('single_template', 'shop_template_switch');3. 完成
URLに各パラメーターを追加し、表示されれば完成です。
https://hogehoge.com/cat/美味しい餃子渋谷店/
https://hogehoge.com/cat/美味しい餃子渋谷店/?info=menu
https://hogehoge.com/cat/美味しい餃子渋谷店/?info=accessパラメーター名でテンプレを作れば、さらにページを増やしていけます。
例:メッセージページを以下のURLで作成する場合
https://hogehoge.com/cat/美味しい餃子渋谷店/?info=message
→ single-message.php特定のカスタム投稿タイプの場合
複数のカスタム投稿タイプがある場合など、特定のカスタム投稿タイプだけに反映させたいこともあると思います。
function.phpの記述を以下のようにすれば、特定のカスタム投稿タイプだけで実装することができます。例:カスタム投稿タイプ「shop」にだけ適用したい場合
function.phpfunction shop_template_switch($template) { $new_template = $template; $post = get_queried_object(); if (is_singular( 'shop', $post)) { if (isset($_GET['info'])) { $new_template = 'single-' . esc_html($_GET['info']) . '.php'; if (is_array($template)) { $new_template = array( $new_template, isset($template[1]) ? $template[1] : 'single-shop.php' ); } else { $new_template = preg_replace('/[^\/]+\.php$/i', $new_template, $template); if (!file_exists($new_template)) { $new_template = $template; } } } } return $new_template; } add_filter('single_template', 'shop_template_switch');参考サイト
- 投稿日:2019-12-05T21:08:43+09:00
wordpress 公開サイト移行のちょっとした手順
はじめに
案件でwprdpress構築を任せていただき、クライアントオッケーがでて、いよいよ公開!のタイミングになりました。
スムーズにクライアント希望時間に公開できるようにいろいろと下準備をしましたので、メモ代わりに書きます。
(実際には、まだ公開はしていないのでミスが発覚したら、随時書き換えます。)経緯
とあるサイトにて、40ページ近くあるコンテンツを最初はhtmlで組んでほしいとの依頼で制作をしたものの、やはり自分たちで更新をしたいからCMS(wordpress)で作り直してほしいとのことで再度制作。
環境
新規でドメイン・サーバー(xさん)を契約し、htmlファイルを置いていました。
サブドメインにwordpressをインストールし、basic認証をかけながら構築を進めました。
なので、実質wordpressでの新規のサイト構築といったかんじです。
cssやjsは手を加えなくて済んだんで、助かりました〜。WP構成
サブフォルダ
wordpressのインストール先をテスト・公開ともに、フォルダを一つかましています。
home/public_html/__cms
こうすることで、__cms
と同階層にindex.php
を置かない限り、テーマは表示されない。(ディレクトリアクセスされたら、エラーは出ますが少しの間なのでokとしておきます。)
なので、公開サイトに本来の静的ファイルを公開しつつ、裏ではテーマ移行を進めておくことができる。
worpressを本番サイトにインストールして、テスト環境とは別にデータベースも作成しておく/assets/
また、
__cms/
と同階層にassets/
をおき、その中にcss・js・img
等をぶち込んでおく。必要なのはimg
のみですが
こうすることで、静的ページなんかは長ったらしい画像パスを書かなくても、/assets/path/to/img
で読み込むことができる。
※元々の静的ファイルも画像パスは、全てルートパス読み込みしています。データベース
エクスポート
phpMyAdminを使用して、テストサイトのデータベースを複製(エクスポート)する。
- エクスポート方法は、全てのテーブルをインポートする場合は「詳細」(必要に応じて除外選択できる)を選択する。
- 生成オプションについては、
DROP TABLE / PROCEDURE / FUNCTION ...
を選択しておくといいらしい。(ドキュメント参照)DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER コマンドを追加する」オプションを選択します。復元するデータベース上でテーブルを作成する前に、同名のテーブルが存在すれば DROP コマンドでこれを削除します。
- あとは触らなくても、デフォルトで基本おkのはず。今回は特に触っていません。
そしたら、sqlファイルが出力されます!
インポート
今度は、新しく作成したDBに先ほどエクスポートしたDBを反映(インポート)させる。
インポートファイルを読み込んで実行する!
これでDBの複製ができる。注意
今回の案件では一箇所かなとは思いますが、DB内に
ドメイン
が記述されている箇所がある。
そこをテストドメイン→本番ドメインに書き換えないとエラーが起こってサイトが見られない。ここは注意してねと言われただけなので、詳しく理解できていない、後日調べなければ!今回変更したのは以下
wp_options option_name: site_url
wp_options option_name: home
テーマ
テーマの以降をする際は、
duplication
を使うことが弊社では多い。しかし、今回は新規構築とのことで、プラグインを使用せずに、テスト環境から該当ファイルをインポートした。
テーマ移行に必要となるものは以下
- themes / theme / 該当のテーマファイル一式
- uploads (メディアアップの画像等が保管されている)
- plugins インストールされていたプラグイン。これがインポートもエクスポートも時間かかる。我慢。
確認
ひとまず、上記が一通り完了したら、ちゃんと反映されているか確認する。緊張する。
https://domaindesu/__cms/wp-admin/
で管理画面にアクセス。
(本当は、セキュリティのために管理画面URL変えていますが!)
投稿データ等間違えがなければひとまずおっけ公開
サブフォルダにwordpressをインストールすると、同階層に
index.php
を置かないと、サイトアクセスできなくなるので注意。
テストサイトに置いているindex.php
を公開サイトに映して完了(のはず。。。現状、実作業がまだなのでわからない、、、)あと、テーマのパーマリンクディレクトリと既存の静的URL(ディレクトリ)が同一の場合は、静的を消しておかないと、
503エラーが出る。
怖い。
index.php以降のタイミングの前に消しちゃいましょう!スムーズに行くはずといっても、瞬間的にサイトが見れなくなるタイミングはあるので、メンテナンス画面も一緒に準備して、
.htacess
でリダイレクトするとよし!
公開終了後には、コメントアウトするなりしないと、忘れたら怒られる。最後に
本番公開うまくいくといいな。
- 投稿日:2019-12-05T20:12:36+09:00
[jQuery + php] 簡易ソートアプリ作成してみた(デザイン込)
簡易的なアイテムソートできるアプリを実装してみましたので
記録しておきます。成果物
コア機能
○フィルターソート機能(jQuery)
|- カテゴリーボタンを押したらカテゴリーのアイテムのみ表示○一部php化(v5.6)
|- 簡易アプリなのでデータベースは使用なし
|- データベースを利用してもできる形にしているつもりです(問題あったらこちらの記事下にリプください)データダウンロード
こちらにパッケージ化してありますので、
下記コマンドでクローンし「/php_app/filter_search_php/」のフォルダを
Xamppまたはphpを動かせる場所に入れてください。$ git clone https://d-mori-570415@bitbucket.org/d-mori-570415/webdesigntemplate.gitjQueryでフィルターソート実装/phpアプリ化
もともと作成していたWebデザインをベースに実装しています。
データはphp上で記述し、データベースは使っていません。
本来はデータベースを作成し、そちらからデータを取るようにしてください。※コアな部分しか明記しませんので、ダウンロードして見てください。
php<?php /* db設計(Laravelの場合、id、timestampは自動で生成されるので省略) * title * comment * category * image */ $listItems = [ [ 'title'=>'food1', 'comment'=>'これはfood1の画像です', 'category'=>'food', 'image'=>'ph1.jpg' ], [ 'title'=>'food2', 'comment'=>'これはfood2の画像です', 'category'=>'food', 'image'=>'ph2.jpg' ], [ 'title'=>'view1', 'comment'=>'これはview1の画像です', 'category'=>'view', 'image'=>'ph3.jpg' ], [ 'title'=>'view2', 'comment'=>'これはview2の画像です', 'category'=>'view', 'image'=>'ph4.jpg' ], [ 'title'=>'sweets1', 'comment'=>'これはsweets1の画像です', 'category'=>'sweets', 'image'=>'ph5.jpg' ], [ 'title'=>'sweets2', 'comment'=>'これはsweets2の画像です', 'category'=>'sweets', 'image'=>'ph6.jpg' ], ]; // アイテムの合計数を取得 $allListItemsCount = count($listItems); ?> <!DOCTYPE HTML> <html lang="ja"> <head> <!--省略--> <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script src="./js/jquery.min.js"></script> <!--省略--> </head> <body> <div class="container"> <div class="header"> <ul> <li><a href="">Work</a></li> <li><a href="">About</a></li> <li><a href="">Contact</a></li> </ul> <div class="logo"><a href="">DesignWorks<span>®️</span></a></div> </div> <div class="navi"> <h1>Work: <?= $allListItemsCount; ?></h1> <ul class="filter-button"> <li><a href="" class="allItem">ALL</a></li> <li><a href="javascript:void(0)" class="food">FOOD</a></li> <li><a href="javascript:void(0)" class="view">VIEW</a></li> <li><a href="javascript:void(0)" class="sweets">SWEETS</a></li> </ul> </div> <div class="contents"> <div class="items filter-list"> <?php foreach ($listItems as $listItem): ?> <div class="item <?= $listItem['category']; ?>"> <img src="./img/<?= $listItem['image']; ?>"> <a href=""> <div class="mask"> <div class="caption"> <div class="caption-title"><?= $listItem['title']; ?></div> <div class="caption-content"><?= $listItem['comment']; ?></div> </div> </div> </a> </div> <?php endforeach; ?> </div> </div> <div class="footer"> <p>Copyright© 2019 Sample Example All rights reserved.</p> </div> </div> <script> $(function(){ // filter let setFilter = $('.filter-button'), filterBtn = setFilter.find('a'), setList = $('.filter-list'), filterList = setList.find('.item'), listWidth = filterList.outerWidth(); filterBtn.on('click', function(){ if(!($(this).hasClass('active'))){ let filterClass = $(this).attr('class'); filterList.each(function(){ if($(this).hasClass(filterClass)){ $(this).css({ display: 'block' }) .stop().animate({ width: listWidth }, 100); $(this).find('*').stop().animate({ opacity: '1' }, 100); } else { $(this).find('*').stop().animate({ opacity: '0' }, 100); $(this).stop().animate({ width: '0' }, 100, function(){ $(this).css({ display: 'none' }); }); } }); filterBtn.removeClass('active'); $(this).addClass('active'); } }); }); </script> </body> </html>
- 投稿日:2019-12-05T20:01:35+09:00
Herokuで自分のサイトを公開する方法 〜DBとの接続、GitHubからdeployまで〜
自分で作ったサイトを簡単に公開できる、Herokuの導入方法について紹介します。
Herokuとは??
Heroku(ヘロク)は大まかに説明すると、PHPやPythonなどのプログラミング言語を用いて開発したWebアプリ、WebサービスなどをWeb上で簡単に公開できるというサービスです。今回はPHPで作ったサイトを公開することをメインに説明していきます。
事前準備
まずは、進めていくに当たってこの準備が必要になります。
まだの方のために隣にリンク貼っときます!
- Herokuのアカウント作成 https://jp.heroku.com/
- GitHubのアカウント作成 https://github.com/
- Sourcetreeのインストール https://www.sourcetreeapp.com/
- homebrewのインストール https://brew.sh/index_ja
heroku CLI(旧Heroku toolbelt)のインストール
こちらからheroku CLIをインストールできます。
Heroku CLIとは、Herokuをコマンドで操作するためのツールになります!https://devcenter.heroku.com/articles/heroku-cli
今回はターミナルからインストールしていきます。
以下のコードを入力してインストールします。$brew tap heroku/brew && brew install herokuインストールが無事終了したら、
$heroku loginを入力するとherokuのサイトにとんでログインできます。
HerokuからCreate a new appを作成
ログインしたら、SourcetreeでローカルのフォルダからGitHubへpushした自分のWebサイトを用意しておきましょう!
SourcetreeはローカルのファイルをGitHubへpushするために必要になるのですがここではその説明は省きます。https://dashboard.heroku.com/apps
このURLをクリックすると、このような画面になると思います。
ここのCreate new appをクリック。
するとこのような画面になると思います。App nameに入力した内容はアクセスする際のURLの一部分になります。
例えば、App nameにhoge
と入力すると、URLはhttps://hoge.herokuapp.com
となります。regionはそのまま「United States」でオッケーです。
GitHubからdeploy
Herokuでアプリを作成したら、「Deploy」をクリックして、Deployment methodからGitHubを選択。
ここで、選択する際に「Connect to GitHub」というボタンが表示されるので、それをクリックして
Herokuと自分のGitHubを紐付けましょう。
次に、Connect to GitHubのところに検索欄があるので、そこに自分のGitHubの公開させたいリポジトリを検索して「connect」ボタンをクリックします。うまくいったら、画面下に行くとこのような画面があるはずです。
「Automatic deploys」は自動でdeployする場合、
「Manual deploy」は手動でdeployする場合にそれぞれ用います。どちらの方法でdeployするか選択したら、「Enable Automatic Deploys」または「Deploy Branch」をクリック。
これでGitHubからのdeployは完了です!!
ちゃんと公開されてるか確認したい場合は先ほど作成したURLをブラウザで入力すると確認できます。
ここまでが公開までの手順です!Heroku Add-onsからClearDB MySQLをインストール
続いて、HerokuにDBを接続していきます。Resourcesをクリック。
Find more add-onsボタンをクリックすると別のサイトにとぶので、そこから「ClearDB MySQL」を見つけてクリック。
クリックして「ClearDB MySQL」のページに入ったら「Install ClearDB MySQL」ボタンをクリックします。
注意 : ここでクレカ登録をする必要があります。
クリックするとこんな画面にいくかと思います。
「Add-on plan」はDBの容量になります。Free(無料)だと5MBになります。もっとDBの容量を増やしたいよ、という方は月間でそれぞれの容量に応じた金額を支払う必要があります。
「App to provision to」にはHerokuの「create a new app」であらかじめ作成しておいたアプリの名前を入力します。名前を入力するとこんな感じで「Provision add-on」のボタンが押せるようになるかと思います。
「Provision add-on」をクリック。無事インストールが終わったら、Resourcesのところに「ClearDB MySQL」が追加されてることを確認します。
Sequel Proのインストール
DBの操作するために、Sequel Proをダウンロードします。
こちらからダウンロードできます。
https://www.sequelpro.com/インストールしてアプリを開くと、こんな画面が表示されると思います。
ここに入力する内容は先ほど追加した「ClearDB MySQL」で取得されるデータを入力します。
一度Herokuの自分のアプリ編集画面に戻って、
Settingsの「Config Vars」から確認できると思います。CLEARDB_DATABASE_URLの値から取得できます。読み方はこんな感じです。
mysql://(--username--):(--password--)@(--hostname--)/(--dbname--)?reconnect=true
ここに該当する内容をさっきの画面に入力します。
Sequel Proのデータベースと、ポートには何も入力しなくて大丈夫です。
一度「接続をテスト」ボタンを押して接続を確認してみてください。入力後、接続できたらオッケーです。サイトで使用するテーブルを作成しておいてください。
これでSequel Proとの接続は完了です!
WebサイトをDBとつなげるためのファイルを作成する
WebサイトとDBをつなげるためのファイルを作成しましょう。
作成したらこのように入力しましょう<?php $host = getenv('ホストネームの値'); //MySQLがインストールされてるコンピュータ $dbname = getenv('DBネームの値'); //使用するDB $charset = "utf8"; //文字コード $user = getenv('ユーザーネームの値'); //MySQLにログインするユーザー名 $password = getenv('パスワード'); //ユーザーのパスワード $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, //SQLでエラーが表示された場合、画面にエラーが出力される PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //DBから取得したデータを連想配列の形式で取得する PDO::ATTR_EMULATE_PREPARES => false, //SQLインジェクション対策 ]; //DBへの接続設定 $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset"; try { //DBへ接続 $dbh = new PDO($dsn, $user, $password, $options); } catch (\PDOException $e) { throw new \PDOException($e->getMessage(), (int) $e->getCode()); }上記5行が先ほどSequel Proに入力した内容です。入力した内容をダイレクトに打ち込んでもうまく行くのですが、GitHubなどで公開されるため、そのまま入力するのはまずいです。
そのため、PHPの定義済み変数の環境変数を使用します。
getenv('')
の''
の部分に値を代入しています。では、この環境変数はどこで定義するのかについて説明します。
Herokuのページに戻ってSettingsの「Config Vars」のところを開きます。
するとこのような欄があるかと思います。
ここの「KEY」に定義する環境変数、
「VALUE」に実際の値を入力します。「KEY」で入力した値を先ほどのファイルに入力することで「KEY」に対応した「VALUE」が読み込まれるという仕組みになってます!
入力が終わったらDBと接続したいPHPファイルに
require_once('DBを接続するファイル名.php');
を入力してください。入力例はこんな感じです。ここでは、WebサイトをDBとつなげるためのファイル名をdbconnect.php
としています。<?php require_once('dbconnect.php'); //DBと紐付ける値 $A = $_POST['a']; $B = $_POST['b']; $C = $_POST['c']; //DBのテーブルに上記の変数を代入する設定 $stmt = $dbh->prepare('INSERT INTO テーブル名 (カラムA, カラムB, カラムC) VALUES (?, ?, ?)'); //(?, ?, ?)に入る値の定義 $stmt->execute([$A, $B, $C]); ?>これでWebサイトとDBをつなげることができるかと思います。
これでDBとの接続も完了です!!以上がHerokuで自分のサイトを公開する方法です。
わからないこと、不明な点があればコメントをお願いします。
- 投稿日:2019-12-05T18:17:44+09:00
【Laravel 6.x】マイグレーションでMySQLデータベースのテーブルを作成・編集する
Laravelではデータベースのテーブルを作成・編集する方法としてマイグレーションという仕組みが用意されている。
前提
- Laravel 6.6.1
- PHP7.3
- MySQL 5.6
マイグレーションの流れ
マイグレーションの流れは大まかに言えば、次の3ステップ。
- マイグレーションファイルを作成する
- マイグレーションファイルを編集する
- マイグレーションを実行する
1.マイグレーションファイルを作成する
マイグレーションファイルを作成するには、
php artisan make:migration
コマンドを使う。テーブルを新規作成する場合とテーブルを編集する場合でオプションが異なる。1-1.テーブルを新規作成する場合
コマンド$ php artisan make:migration 名前 --create=テーブル名このコマンドを実行すると
タイムスタンプ_名前.php
のファイル名で、database/migrations
にファイルが作成される。例えば、名前を
create_users_table
、テーブル名がusers
とすると、コマンドは以下のようになる。コマンド例$ php artisan make:migration create_users_table --create=users上記をコマンドを実行すると、
database/migrations
に2019_12_05_000000_create_users_table.php
が作成される。1-2.テーブルを編集する場合
コマンド$ php artisan make:migration 名前 --table=テーブル名このコマンドを実行すると
タイムスタンプ_名前.php
のファイル名で、database/migrations
にファイルが作成される。例えば、名前を
add_api_token_column
、テーブル名がusers
とすると、コマンドは以下のようになる。コマンド例$ php artisan make:migration add_api_token_column --table=users上記をコマンドを実行すると、
database/migrations
に2019_12_05_083543_add_api_token_column.php
のような名前のファイルが作成される。2.マイグレーションファイルを編集する
作成したマイグレーションファイルと開くと、以下のようになっている。
up
メソッドとdown
が出来ている。作成したファイル例:2019_12_05_083543_add_api_token_column.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AddApiTokenColumn extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { // }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { // }); } }
up
メソッドには追加したい作業を記述し、down
メソッドにはup
メソッドの内容を元に戻す作業を記述する。それぞれSchema::table()
が作成されている。テーブルを作成する、すなわち、make:migration
のオプションを--create
にすると `Schema::create()' が作成されている。例にupメソッドとdownメソッドを追加<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AddApiTokenColumn extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { // api_token カラムを password カラムの次に追加。 // ユニークキーを追加し、 // NULL を許可で、デフォルトをNULLにする。 $table->string('api_token', 80)->after('password') ->unique() ->nullable() ->default(null); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { // api_token カラムを削除 $table->dropColumn('api_token'); }); } }3.マイグレーションを実行する
以下の
php artisan migrate
コマンドを実行することで、マイグレーションファイルの内容がデータベースに反映される。同時にデータベースのmigrations
テーブルにファイル名と実行順序が保存される。コマンド$ php artisan migration
以下のように
Migrating
やMigrated
が表示されたらOK。コマンド実行後の例Migrating: 2019_12_05_083543_add_api_token_column Migrated: 2019_12_05_083543_add_api_token_column (0.01 seconds)
- 投稿日:2019-12-05T17:56:53+09:00
Laravel view composerでロジックを一箇所にまとめよう!
この記事について
あらすじ
自社サービス開発の中で共通でuser情報を使い回している箇所があり、その部分が煩雑な書き方になっていたので、処理をどこか一つにまとめたいと思っていました。
やりたい事
- 共通でuser情報を使い回しているを共通化する。
- ヘッダーのbladeファイル以外でサイドバーなどの箇所でもuser情報を使いたい。
やってみた事
- Laravel view composerを使って、処理を共通化する。
view composerとは?
ビューコンポーザはビューがレンダーされる時に呼び出される、コールバックかクラスメソッドのことです。 ビューがレンダーされるたびに結合したい情報があるなら、ビューコンポーザがロジックを一箇所にまとめるのに役立ちます。
処理イメージ図
どんな時に使うのか?
サイドバーなどすべてのページで共通するデータを表示したい時。
ビューロジックはできればコントローラーに書きたくないし、テンプレートもできるだけロジックを入れず、綺麗なhtmlのままでありたい。
コントローラーの肥大化を防ぎたい。
実装方法
実装手順
- ①サービスプロバイダーの用意
- ②app/config.phpに登録
- ③Composerクラスの作成
- ④SeriviceProviderへの設定
- ⑤bladeファイルの修正
①サービスプロバイダーの用意
$ php artisan make:provider ViewComposerServiceProvider
を実行して、サービスプロバイダーを生成する。<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use View; /** * Class ViewComposerServiceProvider * @package App\Providers */ class ViewComposerServiceProvider extends ServiceProvider { /** * */ public function boot() { } /** * Register any application services. * * @return void */ public function register() { // } }②app/config.phpに登録
「config/app.php」へ下記のようにSeriviceProviderを登録する
<?php 'providers' => [ App\Providers\ViewComposerServiceProvider::class, ],③Composerクラスの作成
「app/Http/ViewComposers」という形でディレクトリを作成し、ViewComposersディレクトリの中に「LayoutComposer.php」を作成します。
<?php namespace App\Http\ViewComposers\User\Worker; use Auth; use Illuminate\View\View; /** * Class LayoutComposer * @package App\Http\ViewComposers\User\Worker */ class LayoutComposer { /** * @param View $view */ public function compose(View $view) { $view->with([ 'loginUser' => Auth::user(), ]); } }④SeriviceProviderへの設定
bootメソッドにViewComposerが値を渡すviewを、View::composersメソッドを使って指定します。
<?php namespace App\Providers; use App\Http\ViewComposers\User\Agent as Agent; use App\Http\ViewComposers\User\FriendRecommendComposer; use App\Http\ViewComposers\User\Worker as Worker; use Illuminate\Support\ServiceProvider; use View; /** * Class ViewComposerServiceProvider * @package App\Providers */ class ViewComposerServiceProvider extends ServiceProvider { /** * */ public function boot() { View::composers([ Agent\LayoutComposer::class => 'agent.*', Worker\LayoutComposer::class => 'worker.*', FriendRecommendComposer::class => 'user.friend_recommend.block', ]); } /** * Register any application services. * * @return void */ public function register() { // } }⑤bladeファイルの修正
$loginUserの変数を該当箇所に書く
<?php <div class="user-block"> <div class="img-wrap"> <img src="{{ $loginUser->userProfile->image->url }}" alt=""> </div> <div class="user-name">{{ $loginUser->userProfile->display_name }}</div> <div class="user-type">{{ \App\Enums\UserRole::toJapanese($loginUser->userProfile->role) }}</div> </div>まとめ
- 共通で使うような変数やロジックがあればview composerにまとめよう!
参考記事
- 投稿日:2019-12-05T17:55:54+09:00
PHPのバージョンを7.4にアップデートする
課題
古くなったPHPのバージョンをアップデートする
現状のバージョンを確認する
下記のコマンドでバージョン確認。
ターミナル$ php -v PHP 7.1.30 (cli)*ちなみにですが今回はHomebrewを使ってアップデートしようと思います。
Homebrewのインストールがまだという方はこちらをソッと置いておきます
(https://brew.sh/index_ja.html)PHP7系を検索する
次にHomebrewコマンドを使って現在インストールができるPHPのバージョンを検索します
ターミナル$ brew search php@7 ==> Formulae php@7.2 php@7.3 php@7.4最新のPHP 7.4 をインストールしてみる
どのバージョンをインストールするか決めたら
PHP 7.4を Homebrew経由でインストールします。ターミナル$ brew install php@7.4PATHを通そう
インストールは無事にされたと思いますが、まだ終わりではありません。
PATHを通す必要があるので下記コマンドにて設定を上書きします。ターミナル$ echo 'export PATH="/usr/local/opt/php@7.4/bin:$PATH"' >> ~/.bash_profile $ echo 'export PATH="/usr/local/opt/php@7.4/sbin:$PATH"' >> ~/.bash_profilePHPとの冒険の再出発へ
上書きまで できたら下記コマンドでPHPを再スタートさせます。
ターミナル$ brew services start phpここで一旦ターミナルを閉じます。
大事なことなのでもう一度言います。ここで一旦ターミナルを閉じます。
ターミナルをもう一度起動させたらバージョンを確認してみます。
ターミナル$ php -v PHP 7.4.0 (cli)これで完了です!
- 投稿日:2019-12-05T17:21:52+09:00
symfony/mailer でTwigを使ってテキストメールをbase64で送る。
Symfony Advent Calendar 2019 15日目の記事です。
はじめに
メールを業務で利用する場合は、定型文を用意して、例えば、お客様の名前とか日付とかを、送信する相手毎に置換をしてから送信をすることが多いです。
メールの本文を、
str_replace
で置換をしてもよいのですが、テンプレートエンジンのtwigを使ってみたくなります。Twig: HTML & CSSを試す
Twigを使ってメールを送る方法について見ていきます。
Mailerドキュメントに、Twig: HTML & CSSの項目がありますので、そのまま実行してみます。
MailerController.phpclass MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $email = (new TemplatedEmail()) ->from('fabien@example.com') ->to(new Address('ryan@example.com')) ->subject('Thanks for signing up!') // path of the Twig template to render ->htmlTemplate('emails/signup.html.twig') // pass variables (name => value) to the template ->context([ 'expiration_date' => new \DateTime('+7 days'), 'username' => 'foo', ]) ; $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }このソースは、https://github.com/idani/symfony_mailer_tutorial3 で保管しています。
今回のコミットは以下で見れます。
https://github.com/idani/symfony_mailer_tutorial3/commit/39d7caa797f0d1e663ba517553d8b23b8b319410http://localhost:8000/mailer を開くと、以下の画面が表示されました。メール送信ができたようです。一番下のデバッグ用のツールバーのメールアイコンにも、メールが1通送信されたことが表示されていますね。
http://127.0.0.1:8025/ で動作しているMailDevを確認すると、1通メールが届いていました。
デフォルトで表示されるのは、TwigでレンダリングされたHTMLメールになります。MailDevで設定を切り替えて、テキストメールにしてみます。
その場合は、HTMLからテキストを抽出したテキストメールが表示されます。誤解されないように説明すると、MailDevはメールソースに記載されたテキストメールの部分を表示しています。テキストのメールは、symfony/mailerによって、配信前にHtmlからテキストを抽出して、メールソースに組み込まれています。
メールソースは以下です。
Content-Type
がtext/plain; charset=utf-8
と指定されたテキストと、text/html; charset=utf-8
と指定されたHTMLがquoted-printable
でエンコードされて送られています。メールソースFrom: fabien@example.com To: ryan@example.com Subject: Thanks for signing up! Message-ID: <a7169f3be53c5470e3765e9fc649f5b1@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 15:53:55 +0900 Content-Type: multipart/alternative; boundary="_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_" --_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_ Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Welcome ! You signed up as foo the following email: ryan@e= xample.com Click here to activate your account (this link= is valid until December 12th) --_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_ Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable <h1>Welcome !</h1> <p> You signed up as foo the following email:= </p> <p><code>ryan@example.com</code></p> <p> <a href=3D"#"= >Click here to activate your account</a> (this link is valid until De= cember 12th) </p> --_=_symfony_1575528835_8df53d757797744c9475726f052de156_=_--Text Contentを試す。
Mailerドキュメントを読み進めると、Text Contentの部分で、メールに入っているテキスト部分をカスタマイズできるようです。
さっそく、サンプルソースに
->textTemplate('emails/signup.txt.twig')
を追加します。MailerController.php$email = (new TemplatedEmail()) ->from('fabien@example.com') ->to(new Address('ryan@example.com')) ->subject('Thanks for signing up!') // path of the Twig template to render ->htmlTemplate('emails/signup.html.twig') // pass variables (name => value) to the template ->context([ 'expiration_date' => new \DateTime('+7 days'), 'username' => 'foo', ]) ->textTemplate('emails/signup.txt.twig') ; $mailer->send($email);signup.txt.twigWelcome ! You signed up as foo the following email: {{ email.to[0].address }} Click here to activate your account http://www.example.com/xxxxxxxxxxx (this link is valid until {{ expiration_date|date('F jS') }})https://github.com/idani/symfony_mailer_tutorial3/commit/2931651279278b57ad8c3f2bfe045ac03d59f141
実際に送信をされたメールを見ると、修正通りのテキストメールが送られていました。
メールソースFrom: fabien@example.com To: ryan@example.com Subject: Thanks for signing up! Message-ID: <f6cc0e2166ff05ab5816d2bb26255080@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 16:27:04 +0900 Content-Type: multipart/alternative; boundary="_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_" --_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_ Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Welcome ! You signed up as foo the following email: ryan@= example.com Click here to activate your account http:/= /www.example.com/xxxxxxxxxxx (this link is valid until December 12th) --_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_ Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable <h1>Welcome !</h1> <p> You signed up as foo the following email:= </p> <p><code>ryan@example.com</code></p> <p> <a href=3D"#"= >Click here to activate your account</a> (this link is valid until De= cember 12th) </p> --_=_symfony_1575530824_57df7e1ab6fbb47d7ffce4184ba1587c_=_--テキストメールだけ送る
これまではHTMLメールが主役で、テキストメールがおまけ的な感じでした。
Twigを使って、テキストメールだけ送るにはどうしたらよいでしょうか?さきほどのサンプルコードから、
htmlTemplate
を外してみましょう。$email = (new TemplatedEmail()) ->from('fabien@example.com') ->to(new Address('ryan@example.com')) ->subject('Thanks for signing up!') // path of the Twig template to render //->htmlTemplate('emails/signup.html.twig') <=コメントアウト // pass variables (name => value) to the template ->context([ 'expiration_date' => new \DateTime('+7 days'), 'username' => 'foo', ]) ->textTemplate('emails/signup.txt.twig') ; $mailer->send($email);https://github.com/idani/symfony_mailer_tutorial3/commit/8313dcc169ee6cf4f4ab0a0356ea15997aa0bada
MailDevでは、意図した通り、テキストメールが見れました。
画像では変化がないので、メールソースを見ていただくと、テキストメールになっていることがわかります。メールソースFrom: fabien@example.com To: ryan@example.com Subject: Thanks for signing up! Message-ID: <3d16b774412841ddbdef19077a4e8fc2@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 16:33:07 +0900 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Welcome ! You signed up as foo the following email: ryan@= example.com Click here to activate your account http:/= /www.example.com/xxxxxxxxxxx (this link is valid until December 12th)日本語テンプレートで送信をしてみる
問題ないと思いますが、テンプレートを日本語にしてみます。
signup.txt.twigWelcome ! ようこそ ! You signed up as {{ username }} the following email: 次のメールアドレスで{{ username }}としてサインアップしました。 {{ email.to[0].address }} Click here to activate your account アカウントを有効にするにはここをクリックしてください http://www.example.com/xxxxxxxxxxx (this link is valid until {{ expiration_date|date('F jS') }}) (このリンクは、{{ expiration_date|date('F jS') }}まで有効です。https://github.com/idani/symfony_mailer_tutorial3/commit/14a7ee816dbb4acb8e263060e97e8230d785cc99
送信すると、以下のように問題なく日本語も表示されました。
メールソースFrom: fabien@example.com To: ryan@example.com Subject: Thanks for signing up! Message-ID: <0072478296801e97a3472e1ce06a9f38@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 16:41:55 +0900 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Welcome ! =E3=82=88=E3=81=86=E3=81=93=E3=81=9D =EF=BC=81 You sig= ned up as foo the following email: =E6=AC=A1=E3=81=AE=E3=83=A1= =E3=83=BC=E3=83=AB=E3=82=A2=E3=83=89=E3=83=AC=E3=82=B9=E3=81=A7foo=E3=81= =A8=E3=81=97=E3=81=A6=E3=82=B5=E3=82=A4=E3=83=B3=E3=82=A2=E3=83=83=E3=83= =97=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82 ryan@example.com= Click here to activate your account =E3=82=A2=E3=82= =AB=E3=82=A6=E3=83=B3=E3=83=88=E3=82=92=E6=9C=89=E5=8A=B9=E3=81=AB=E3=81= =99=E3=82=8B=E3=81=AB=E3=81=AF=E3=81=93=E3=81=93=E3=82=92=E3=82=AF=E3=83= =AA=E3=83=83=E3=82=AF=E3=81=97=E3=81=A6=E3=81=8F=E3=81=A0=E3=81=95=E3=81= =84 http://www.example.com/xxxxxxxxxxx (this link is valid u= ntil December 12th) =EF=BC=88=E3=81=93=E3=81=AE=E3=83=AA=E3=83=B3= =E3=82=AF=E3=81=AF=E3=80=81December 12th=E3=81=BE=E3=81=A7=E6=9C=89= =E5=8A=B9=E3=81=A7=E3=81=99=E3=80=82Base64で送信する
ここまでできても合格なのですが、可能であれば、
quoted-printable
ではなく、base64
で送信をしたいです。どうしたらよいのでしょうか?
TemplatedEmail.phpを見てみると、Email.phpの派生クラスであることがわかります。
Email.phpの中身は、symfony/mailerでbase64やiso-2022-jpを試してみるで見ていきましたが、
quoted-printable
固定となってしまいます。そうすると、Twigでテキストをレンダリングして、それをメール本文として指定するのが良さそうです。
Creating and Using TemplatesのRendering a Template in Servicesに、サービス内でのレンダリング方法が記載されています。
こちらを参考にして、サンプルコードを変更します。
Base64のメールの作成方法は、symfony/mailerでbase64やiso-2022-jpを試してみるから流用しますので、ガラッと変わります。MailerController.phpmb_language("uni"); mb_internal_encoding("UTF-8"); $subject = mb_encode_mimeheader('Thanks for signing up! 登録してくれてありがとうございます!!'); $subject = str_replace("\r\n", '', $subject); $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', $subject) ; $body = $twig->render('emails/signup.txt.twig', [ 'expiration_date' => new \DateTime('+7 days'), 'username' => 'foo', 'email' => 'you@example.com', ]); $textContent = new TextPart($body, 'utf-8', 'plain', 'base64'); $email = new Message($headers, $textContent); $mailer->send($email);テンプレートの方も、メールアドレスなどの情報も、自動で設定されていたようです。
このためメールアドレスも個別に指定するように変更します。signup.txt.twigWelcome ! ようこそ ! You signed up as {{ username }} the following email: 次のメールアドレスで{{ username }}としてサインアップしました。 {{ email }} Click here to activate your account アカウントを有効にするにはここをクリックしてください http://www.example.com/xxxxxxxxxxx (this link is valid until {{ expiration_date|date('F jS') }}) (このリンクは、{{ expiration_date|date('F jS') }}まで有効です。https://github.com/idani/symfony_mailer_tutorial3/commit/06fd543a301c8bb61f0a362c3fb66e0636d65fd5
想定通り、Twigを使ってテキストメールをBase64で送信することができました。
メールソースFrom: =?UTF-8?B?6YCB5L+h6ICF5ZCN?= <hello@example.com> To: =?UTF-8?B?5Y+X5L+h6ICF5ZCN?= <you@example.com> Subject: Thanks for signing up! =?UTF-8?B?55m76Yyy44GX44Gm44GP44KM44Gm44GC44KK44GM?= =?UTF-8?B?44Go44GG44GU44GW44GE44G+44GZ77yB77yB?= Message-ID: <a98efb8654df5bc230c364e0d1e476f4@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 17:15:07 +0900 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 V2VsY29tZSAhCuOCiOOBhuOBk+OBnSDvvIEKCiAgICBZb3Ugc2lnbmVkIHVwIGFzIGZvbyB0aGUg Zm9sbG93aW5nIGVtYWlsOgogICAg5qyh44Gu44Oh44O844Or44Ki44OJ44Os44K544GnZm9v44Go 44GX44Gm44K144Kk44Oz44Ki44OD44OX44GX44G+44GX44Gf44CCCgogICAgeW91QGV4YW1wbGUu Y29tCgoKICAgIENsaWNrIGhlcmUgdG8gYWN0aXZhdGUgeW91ciBhY2NvdW50CiAgICDjgqLjgqvj gqbjg7Pjg4jjgpLmnInlirnjgavjgZnjgovjgavjga/jgZPjgZPjgpLjgq/jg6rjg4Pjgq/jgZfj gabjgY/jgaDjgZXjgYQKCiAgICBodHRwOi8vd3d3LmV4YW1wbGUuY29tL3h4eHh4eHh4eHh4CiAg ICAodGhpcyBsaW5rIGlzIHZhbGlkIHVudGlsIERlY2VtYmVyIDEydGgpCiAgICDvvIjjgZPjga7j g6rjg7Pjgq/jga/jgIFEZWNlbWJlciAxMnRo44G+44Gn5pyJ5Yq544Gn44GZ44CCCg==TwigのテンプレートをDBに保存する
メールの本文をテンプレートという形で保存をして、レンダリングすることもできました。
ところが、メールは本文以外にサブジェクトというデータも必要です。
サブジェクトはDBに保存し、メールの本文のテンプレートはファイルシステムというのは微妙ですよね。TwigのテンプレートもDBに保存できるとよいですね。
調べてみると、Twigのドキュメントで、Using a Database to store Templatesがありました。
ただ、こちらは純粋にTwigのテンプレートの読み出し先をDBにするということみたいです。
サイトとしてHTMLを表示するためでしたらよいのですが、メールとしては使いづらいですね。
それよりは、その次に記載のTwigのテンプレートを変数として受け取って処理をするための、Loading a Template from a Stringが良さそうです。
サンプルでテンプレートを読み込む部分を書き換えます。
MailerController.phppublic function index(MailerInterface $mailer, Environment $twig) { mb_language("uni"); mb_internal_encoding("UTF-8"); $subject = mb_encode_mimeheader('Thanks for signing up! 登録してくれてありがとうございます!!'); $subject = str_replace("\r\n", '', $subject); $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', $subject) ; $twigEmailTemplate =<<<EOL Welcome ! ようこそ ! これはTwigテンプレートを変数から読み込んでレンダリングしたメールです。 You signed up as {{ username }} the following email: ~~~中略~~~ (このリンクは、{{ expiration_date|date('F jS') }}まで有効です。 EOL; $template = $twig->createTemplate($twigEmailTemplate); $body = $template->render([ 'expiration_date' => new \DateTime('+7 days'), 'username' => 'foo', 'email' => 'you@example.com', ]); $textContent = new TextPart($body, 'utf-8', 'plain', 'base64'); $email = new Message($headers, $textContent); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); }https://github.com/idani/symfony_mailer_tutorial3/commit/c97d5ac4a4e02c6c74111aa3b249f7abb2a0fc5c
まとめ
Twigを使って簡単にメールが送れることがわかりました。
Base64にするには、面倒なので、良さそうな修正案を考える必要てプルリクした方がよいですね。
- 投稿日:2019-12-05T17:19:38+09:00
MAMP PHP7.3.1とVSCodeでXdebugを使う
よくわかっていないので、間違っているところ・余計なところ・足りないところがあれば教えて下さい。
MAMP 5.3
PHP 7.3.1
Xdebug v2.8.1homebrewを使います。
Xdebugをインストールする
/Applications/MAMP/bin/php/php7.3.1/lib/php/extensions/no-debug-non-zts-20180731/
の中身を見てもxdebug.so
がなかったので、インストールしました。以下のリンク先に手順の案内があります。
Xdebug: Support — Tailored Installation Instructions詳しく説明していきます。
↓ まず、MAMPの
Start Servers
を押した後、Open WebStart page
を押します。
PHPINFOのHTMLソースをすべてコピーします。
先程のリンク先にソースを貼る場所があるので、そこに貼ります。
そうすると、ダウンロード手順が案内されます。まず、
xdebug-2.8.1.tgz
をダウンロードします。ターミナルを開き、ダウンロードしたファイルがあるディレクトリに
cd
コマンドで移動します。
そこで以下のコマンドを実行してファイルを解凍します。ターミナル$ tar -xvzf xdebug-2.8.1.tgz↓ 解凍したファイルに移動して、
phpize
を実行します。ターミナル$ cd xdebug-2.8.1 $ phpize # こんな表示が出ます Configuring for: ... Zend Module Api No: 20180731 Zend Extension Api No: 320180731もしもここでエラーが出た場合はautoconfをインストールします。
ターミナル$ brew install autoconf成功したら以下を実行します。
ターミナル$ ./configure $ make
xdebug.so
をMAMPの中にコピーします。ターミナル$ cp modules/xdebug.so /Applications/MAMP/bin/php/php7.3.1/lib/php/extensions/no-debug-non-zts-20180731php.iniの設定を変更する
/Applications/MAMP/bin/php/php7.3.1/conf/php.ini
の設定を変更します。
一番下に以下の記述を足します。
zend_extension="~"
の行がコメントアウトされている場合は先頭の;
を削除します。/Applications/MAMP/bin/php/php7.3.1/conf/php.ini[xdebug] zend_extension="/Applications/MAMP/bin/php/php7.3.1/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so" xdebug.remote_enable=1 xdebug.remote_autostart=1MAMPの
Stop Servers
、Start Servers
を押して再起動をしてください。ターミナルでMAMPのPHPを実行するようにする
大変参考にさせていただきました。
・MAMPのPHPをターミナルから実行する方法 - Qiitaパスを通します。
.bashrc
に以下を記述します。bashではなくzshなどの場合は、がんばってください。.bashrcexport PATH=/Applications/MAMP/bin/php/php7.3.1/bin:$PATHターミナル# パスの確認 $ which php /Applications/MAMP/bin/php/php7.3.1/bin/php確認する
ターミナル$ php -m (省略) [Zend Modules] Xdebug正しく動いていれば、オレンジ色のエラーがブラウザに表示されたり、var_dump()が見やすくなったりします。
Visual Studio Codeに拡張機能をインストールする
拡張機能 PHP Debug をインストールします。
VSCodeの左側にある虫のマークをクリックし、デバッグ画面を開きます。
デバッグ画面の上部にある歯車のマークをクリックし、PHPを選択します。
自動でlaunch.json
が作成されます。デバッグする
MAMPを起動します。
VSCodeでブレークポイントを打ちます。
↓ 左の丸印です。
Listen for XDebugを選択し、三角の再生マークを押します。
ブラウザを開き、ページを更新すると、VSCodeに自動で戻ってきます。
変数の中身とか見れます。
エラーとかも見れます。
以上です。
- 投稿日:2019-12-05T16:46:25+09:00
ログイン必須なLaravelシステムでファイルの保存と取得を行う
ログイン必須なLaravelシステムでファイルの保存と取得を行う
ログイン必須なLaravelシステムでファイルの保存と取得の方法を軽く調べてみました。
要約
だいたいドキュメントに書いてある。。。
- Storageを使う
- ファイルの操作はLaravelのドキュメントの通り。
- ファイルの保存場所のシンボリックリンクをpublicに作らない
- コントローラでファイルを取得して返すやりたいこと
- Laravelでファイルの保存と取得
- ファイルはログイン済みのユーザのみが見れる
- 非ログインユーザは見れない
方法
authのミドルウェアを指定したコントローラを経由してファイルを返すようにする。
ファイルの保存
use Illuminate\Support\Facades\Storage; Route::group(['middleware' => 'auth'], function() { Route::get('/', function() { $content = "hello world\n"; Storage::put('test.txt', $content); return "saved!"; }); });これで
$content
の内容がstorage/app/text.txt
に保存される。ファイルの取得
use Illuminate\Support\Facades\Storage; Route::group(['middleware' => 'auth'], function() { Route::get('/test.txt', function() { $content = Storage::get('test.txt'); return $content; }); });最後に
簡単にできた。あとは、コントローラでファイルに対しての認可を行ったりすれば、もっと複雑なことができそうです。
- 投稿日:2019-12-05T15:54:04+09:00
Moodle 3.8 マニュアル - Debian ベースのディストリビューションに Moodle をインストールする
Installing Moodle on Debian based distributions
Contents1 Debian パッケージとして Moodle をインストールする
2 moodle を .tgz(.tar.gz) あるいは .zip ファイルからインストールする
2.1 ステップ 1:必要なパッケージをインストールする
2.2 ステップ 2:MySQL データベースをセットアップする
2.3 ステップ 3:moodle をダウンロードする
2.4 ステップ 4:ファイルを解凍する
2.5 ステップ 5:データディレクトリを作成し Moodle ディレクトリの権限を設定する
2.6 ステップ 6:Apache が Moodle を web サイトとして使用するように変更する
2.7 ステップ 7:最後にインストールする
2.8 ステップ 8:Moodle をセットアップする
2.9 ステップ 9:cron をインストールする1 Debian パッケージとして Moodle をインストールする
Moodle は Debian パッケージとして含まれていました。Moodle をどんな種類のインストーラーによっても、インストールすることは推奨されません。結局は、手動で(あるいは git を使用して)インストールすることが、コードがオリジナルから変更されていないために、もっとよいということになります。
2 moodle を .tgz(.tar.gz) あるいは .zip ファイルからインストールする
2.1 ステップ 1:必要なパッケージをインストールする
After installing your Debian distro, install these packages (if you've not already done so). See Installing Apache, MySQL and PHP or refer to the respective user manuals. Using apt-get, aptitude or synaptic you can install these very easily.
- Web Server (Apache highly recommended)
- Database Server (MySQL or PostgreSQL recommended)
- PHP, PHP-MySQL mod (or mod for your database)
- Various PHP modules necessary for Moodle
LAMP in Debian
Setting up a LAMP in Debian is very easy. Once you get used to Debian administration including installation and configuration are much simpler compared to other linux distros. The following describes how to install apache, php and mysql on the Debian distribution.
For installation of the necessary packages the easiest option to use apt-get through the command line interface. Debian has easy access to a Root Terminal
Use the following command to install apache2, php5 and mysql (Note: Debian is now shipping with MariaDB as default database server - I have included instructions on how to install MySQL community edition instead.) MySQL Server Package install:
wget https://dev.mysql.com/get/mysql-apt-config_0.8.6-1_all.deb mysql-apt-config_0.8.6-1_all.deb apt-get install gdebi-core gdebi mysql-apt-config_0.8.6-1_all.debUpdate Repositories:
apt-get updateInstall LAMP:
apt-get install apache2 php7.1 mysql-server php7.1-mysql libapache2-mod-php7.1 php7.1-gd php7.1-curl php7.1-xmlrpc php7.1-intl php7.1-zip php7.1-mbstringThe mentioned packages are installed along with the dependencies depending on what was already installed on your Debian system.
Now you may fire up a browser and type localhost to check whether the apache2 default page is shown,
If you are familiar with apache settings, you can edit the apache configuration files using the text editor gedit or nano by typing:
gedit /etc/apache2/apache2.confor
nano /etc/apache2/apache2.confHowever, for a basic install, you should not need to mess with this file.
Now we must make a few changes in the php7.1 configuration file. Open it using
gedit /etc/php/7.1/apache2/php.inior
nano /etc/php/7.1/apache2/php.iniadd the entries
extension=mysql.so extension=gd.soSometimes these entries are provided as example lines being commented out . You can remove the commenting to activate the entries. Then make the following changes (adjust to your preference) This will allocated more memory and allow files to be uploaded up to 80MB. This should be enough for most multi-media files. Hard drive space is cheap and the default is only 2MB. It is recommended that you change the settings to the following values:
memory_limit = 40M post_max_size = 80M upload_max_filesize = 80MTo test the php installation, you can create a text file named phpinfo.php with the contents <?phpinfo()?> and save it at /var/www. Restart apache with the command below. Now access this file through the browser localhost/phpinfo to check the installation of php.
You can restart apache 2 by
/etc/init.d/apache2 restartor
service apache2 restart2.2 ステップ 2:MySQL データベースをセットアップする
Set a secure root password for the database (you might have already done this in the initial install process)
mysqladmin -u root password "mySecurePassword"Note that on a secure production server, you will want to create a different user than root to access the database.
Now log in
mysql -u root -pEnter your password
Create the Moodle database
mysql> CREATE DATABASE moodle CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;(this should set the database up correctly to work with Moodle)
Then exit the database
mysql> exit;You can restart mysql by
/etc/init.d/mysql restart2.3 ステップ 3:moodle をダウンロードする
Download moodle from http://download.moodle.org/?lang=en.
2.4 ステップ 4:ファイルを解凍する
- For zip file, use unzip . For tgz(tar.gz), use tar -zxvf . You can also use any of the GUI front-ends such as file-roller or ark or just let Debian unzip it with Archive Manager. You will get a folder moodle (or moodle-2.8.1 or something similar). It will probably be located in your Downloads folder unless you told the Archive Manager or the Download utility to save it somewhere else. We will presume it is in your Download folder.
From terminal type
cd /home/YourUSERNAME/Downloads mv moodle /var/wwwYou can also copy instead of moving it in case you want a backup of the code on your machine.
cp -R moodle /var/wwwYou will probably need to do this as root (no problem if you are using the Root Terminal!)
2.5 ステップ 5:データディレクトリを作成し Moodle ディレクトリの権限を設定する
Switch to the correct directory
cd /var/wwwCreate a directory for user and course files
mkdir moodledataSet ownership and permissions so that Apache can access the files
chown -R www-data:www-data moodle chown -R www-data:www-data moodledatachmod -R 755 moodle chmod -R 755 moodledataOn a production server, you may want to tighten up privileges.
2.6 ステップ 6:Apache が Moodle を web サイトとして使用するように変更する
Note that the server comes with Apache running and looking at the /var/www directory. But there is nothing in that folder, so one just gets a redirect. Edit as follows to have it point at Moodle instead:
gedit /etc/apache2/sites-available/default or nano /etc/apache2/sites-available/defaultOn about line 4, change
DocumentRoot "/var/www/html"
to
DocumentRoot "/var/www/moodle"
On about line 10, change
<Directory "/var/www/html/">
to
<Directory "/var/www/moodle/">
Around line 17, comment out the line for the default page:
# RedirectMatch ^/$ /apache2-default/
You can change other values like ServerAdmin if appropriate. For all changes, you should restart Apache for the new settings to take effect.
/etc/init.d/apache2 restart2.7 ステップ 7:最後にインストールする
- Go to http://localhost/moodle and follow the instructions.
2.8 ステップ 8:Moodle をセットアップする
If you are only going to test Moodle on your internal network, just enter the local IP address as the web address. You can find the local IP address under DHCP by typing
ifconfig eth0If you have a web address that points to your server, use that instead.
From a browser on another machine, enter
http:// ----- your web address -----
- For the database user, enter root
- For Password, enter the password for the database that you created earlier
Continue through the dialogs until installed.
2.9 ステップ 9:cron をインストールする
Moodle needs a periodic call to do maintenance work like sending out emails, cleaning up the database, updating feeds, etc. To run the cron every 10 minutes, do the following
crontab -u www-data -eAdd this line and save.
*/10 * * * * /usr/bin/php /var/www/moodle/admin/cli/cron.php >/dev/nullUse CNTL-X to save
- 投稿日:2019-12-05T15:38:52+09:00
【 Laravel 6 】ユーザーログイン機能の実装
【 Laravel 6 】ユーザーログイン機能の実装
2019年11月から、Laravel歴数ヶ月の初心者が投稿型ナレッジベースのコミュニティサイトを作るというチャレンジ中。作りたいアプリケーション→機能を因数分解→ググる→先人の轍をたどる(写経する)→ぬかるみにはまる→エラー解消の神を探す→解決を繰り返す日々( ·ㅂ·)و 。備忘録として、Qiitaに投稿しています。
ユーザーログイン機能を作成
下記URLに倣って、ユーザーログイン機能を実装。
Laravel 6「make:Auth」が無くなった 〜Laravel6でのLogin機能の実装方法〜MyMemo
https://qiita.com/daisu_yamazaki/items/a914a16ca1640334d7a5ターミナル$ php artisan serve下記の通り、画面右上に、「LOGIN」と「REGISTER」が表示されていればOK。
〈ブラウザデカクニン〉
以上です(ง ´͈౪`͈)ว
- 投稿日:2019-12-05T15:31:04+09:00
【 Laravel 6 】新規プロジェクト作成
【 Laravel 6 】新規プロジェクト作成
2019年11月から、Laravel歴数ヶ月の初心者が投稿型ナレッジベースのコミュニティサイトを作るというチャレンジ中。作りたいアプリケーション→機能を因数分解→ググる→先人の轍をたどる(写経する)→ぬかるみにはまる→エラー解消の神を探す→解決を繰り返す日々( ·ㅂ·)و 。備忘録として、Qiitaに投稿しています。
新規プロジェクト作成
MAMP htdocsディレクトリでLaravelの新規プロジェクトを作成
(˘ਊ˘)コマンドタタクヨターミナル$ Laravel new sample_board作成したプロジェクトのディレクトリへ移動
(˘ਊ˘)コマンドタタクヨターミナル$ cd sample_board仮想サーバーの立ち上げ
ターミナル$ php artisan serveLaravel development server started: http://127.0.0.1:8000と表示されればOK。
ローカルホストでLaravelのホーム画面が表示されればOK
以上です(ง ´͈౪`͈)ว
- 投稿日:2019-12-05T15:16:46+09:00
JenkinsがPHPUnitの結果ファイルをパースしなくなったときの対処方
JenkinsでxUnit pluginを使う場合、PHPUnitでjunit形式のログを出力しますが、あるときから、以下のようなエラーが大量に出力され、解析に失敗するようになりました。
type.3.2.2: 要素'testsuite'に属性'assertions'を含めることはできません。 WARNING: At line 2161 of file:/var/lib/jenkins/workspace/hoge/reports/phpunit.xml:cvc-complex-type.3.2.2: 要素'testsuite'に属性'warnings'を含めることはできません。 WARNING: At line 2161 of file:/var/lib/jenkins/workspace/hoge/reports/phpunit.xml:cvc-pattern-valid: 値'2.395662'は、タイプ'SUREFIRE_TIME'のパターン'(([0-9]{0,3},)*[0-9]{3}|[0-9]{0,3})*(\.[0-9]{0,3})?'に対してファセットが有効ではありません。いろいろと調べたんですが、これという解決法が見つからなかったため、xUnit pluginが文句を言わないよう、出力されたログファイルを変換することで対処しました。
$project_root/tools/convert_phpunit_log.php<?php $buf = file_get_contents(__DIR__ . '/../reports/phpunit.xml'); $doc = new DOMDocument(); $doc->loadXML($buf); $doc->formatOutput = true; $nodes = $doc->getElementsByTagName('testcase'); foreach ($nodes as $node) { $node->removeAttribute('class'); $node->removeAttribute('file'); $node->removeAttribute('line'); $node->removeAttribute('assertions'); $time = round($node->getAttribute('time'), 3); $node->setAttribute('time', $time); } $nodes = $doc->getElementsByTagName('testsuite'); foreach ($nodes as $node) { $node->removeAttribute('assertions'); $node->removeAttribute('warnings'); $node->removeAttribute('file'); $time = round($node->getAttribute('time'), 3); $node->setAttribute('time', $time); } file_put_contents(__DIR__ . '/../reports/phpunit.xml', $doc->saveXML());入出力のパスは適切なものに変更してください。
これをphpunit終了後に実行してください。
- 投稿日:2019-12-05T14:43:18+09:00
【 Laravel 6 】投稿の表示・修正・更新・削除
【 Laravel 6 】投稿アプリの基本的機能(一覧表示・詳細表示・修正・更新・削除)を実装する
2019年11月から、Laravel歴数ヶ月の初心者が投稿型ナレッジベースのコミュニティサイトを作るというチャレンジ中。作りたいアプリケーション→機能を因数分解→ググる→先人の轍をたどる(写経する)→ぬかるみにはまる→エラー解消の神を探す→解決を繰り返す日々( ·ㅂ·)و 。備忘録として、Qiitaに投稿しています。
一覧表示・詳細表示・修正・更新・削除
投稿型ナレッジベースということで、「投稿アプリ」を軸に色々と機能を付加していきます。
「投稿アプリ」については、こちら↓のサイトを参考に(ほぼ写経)させてもらいました アザマス!!m(__)m【入門】Laravelチュートリアル – 掲示板を作成してみよう
https://blog.hiroyuki90.com/articles/laravel-bbs/#i-2備忘録
私自身が素人すぎて、途中でつまずいたところを追記しておきます。
投稿一覧が表示されるViewファイル(resources/views/posts/index.blade.php)について、Laravelのバージョンアップにより非推奨となっている記述があったので、Laravel 5.8以降に推奨となった書き方に修正。
index.blade.php{!! nl2br(e(str_limit($post->body, 200))) !!} ← Laravel5.8から非推奨 {{ \Illuminate\Support\Str::limit($post->body, 200) }} ← こちらの書き方に変更以上です(ง ´͈౪`͈)ว
- 投稿日:2019-12-05T14:31:45+09:00
Laravel 6.x 投稿型ナレッジベースのコミュニティサイトを作る
Laravel歴数ヶ月の初心者が投稿型ナレッジベースのコミュニティサイトを作る
ある趣味を盛り上げるためのコミュニティサイトを作ることを発想。Qiitaや、CrieitといったWeb開発系のものに着想を得て、投稿型のナレッジベースを主軸としたWebアプリケーションを作ります。
だいぶ初心者なので、他の方のブログやQiita投稿をガシガシ写経が基本です。詰まった部分について、備忘録としてQiitaに挙げていく感じでいきます。
機能一覧
入れたい機能は、こちら。順不同で、触れそうな機能から作っていくので、「作る⇄崩し」の繰り返しがありそう。機能実装の都度、Qiita投稿を下記の機能一覧にリンク貼っつけていく。
- Laravelプロジェクト新規作成(←機能では無いですが(·ε·))
- ログイン機能
- ソーシャルログイン機能
- マイページ
- 投稿一覧
- ストック機能
- ユーザープロフィール
- 表示
- 修正・更新・削除
- 投稿の表示・修正・更新・削除
- マークダウンエディタ
- 画像のアップ
- ハッシュタグ機能
- Like機能
- ランディングページ
- 投稿一覧の表示
- カテゴリー別の投稿一覧(Like順or新着順)
- 広告
- 広告(アフェリエイト)
- データベース周り
環境
- Laravel 6.2
- php 7.2
- Bootstrap(とりあえず、動作のみを作成するので”ガワ”は基本ノータッチ・・・の予定)
背景
機能やサービス設計の諸々について考えあぐねていたら、気づけばどんどん時間ばかりが経過してしまっていた・・・(´ε`;)ハァ。アイディア思考ばかりで、コードからもどんどん離れていることに気づき、その焦りとで、結局どっちつかずの日々。
技術力が全く無いのに「考えても始まらんだろう」と思い、「手を動かす」。そして、開発の試行錯誤しながら、サービスをまとめていくということだけを決意。2019年11月中旬に制作開始しました。年越しのお籠り時期を経て、2020年1月中頃までに一通りの機能を回せるようになるといいなぁ・・・と画策中(´-_ゝ-`)
- 投稿日:2019-12-05T13:30:27+09:00
お手軽Hack!
メリークリスマス!
ユアマイスターアドベントカレンダーの24日目を担当させていただきます、インターンの京極と申します。
https://qiita.com/advent-calendar/2019/yourmystar今回は、Hack言語を触ってみた記事を書いてみました。ご興味ある方は年末年始のお休み中にHackしていただければ幸いです。
Hackとは
HackとはHHVMという実行環境上で動くプログラミング言語で、PHPとの互換性を持ち、Facebookによって開発が行われています。
公式サイト
https://hacklang.org/
Github
https://github.com/facebook/hhvm/tree/master/hphp/hackJava言語がJVMという実行環境で動くように、Hack言語は、HHVMという実行環境で動くということです。
公式サイトを見るとジェネリクスをサポートしていたり、ラムダがファーストクラス関数として使えるなど試してみたい気持ちがクリスマスを迎えるにあたり出てきますのでちょっと触ってみたいと思います。
Hack on Docker
マシンの直接インストールするのは避けて、Docker HubからHackの動く環境を作リます。
Dockerのは行っている環境で以下のコマンドを叩くと、そこはすでに白銀の世界ではなく、HHVMの手に入った世界の中です。
docker run -it hhvm/hhvm bash # hhvm --version HipHop VM 4.34.0 (rel) Compiler: 1575318969_858014129 Repo schema: e5c15841820ab38c7ec75ebe5ec9201013caa5bf適当なディレクトリにhello.hackを置いて、実行させてみます。
<<__EntryPoint>> function main(): noreturn { echo "Hello World!"; exit(0); }docker run -it -v $PWD:/app hhvm/hhvm hhvm /app/hello.hack Hello World!おまけにフィボナッチ数を計算するプログラムも書いてみました。普段からPHPを書く方にとっては違和感が少ないと思います。
function fib(int $n): int { if ($n <= 1) return $n; return fib($n - 1) + fib($n - 2); } <<__EntryPoint>> function main(): noreturn { echo fib(40); exit(0); }Webサーバ
調べているとHHVMはWebサーバの機能が組み込まれているということなので起動させてみます。
docker run -it -v $PWD:/var/www/public -p 12345:80 hhvm/hhvm-proxygenこうするとDocker内の/var/www/publicディレクトリに先程の、hello.hackが存在することになりますので、ブラウザからアクセスしてみます。
http://localhost:12345/hello.hackもう少しHackを書いてみる
ここまで環境構築が主だったので、もう少しプログラムを書いてみたいと思います。様々な機能がHackにはありますが、目についた面白い機能を紹介して終わりにしたいと思います。
以下の書き方をすると、 \$inc は \$a という引数を受け取り、 \$b を引数として受け取る関数を返す関数になります。 \$inc3 は \$a が3になったバージョンの関数なので、 \$inc3 に対して10を引数に渡すと、13が返ってくることが確認できます。
このようなカリー化された関数をさらっと書けるというのもHack言語の魅力だと感じました。
<<__EntryPoint>> function main(): noreturn { $inc = $a ==> $b ==> $a + $b; $inc3 = $inc(3); echo $inc3(10); exit(0); }まとめ
ちょっと触っただけでも様々な機能がHack言語にあることが分かりましたので、年末年始中にもう少しHackに触れたいと思います。メリーハッククリスマス!
- 投稿日:2019-12-05T12:56:06+09:00
symfony/mailerでbase64やiso-2022-jpを試してみる
Symfony Advent Calendar 2019 8日目の記事です。
昨日は@brtriverさんのSymfonyの歴史を振り返ってみるでした!はじめに
こちらの記事は、symfony/mailerで試行錯誤したことの続きになります。
前回は、配信処理までの設定で四苦八苦しましたが、今回は送信するメールの文字コードやエンコードの仕方について試行錯誤していきたいと思います。
メールは、インターネット時代のコミュニケーション手段として、
utf-8、iso-2022-jp
などの文字コード7bit, 8bit, base64, quoted-printable
などのエンコーディング- 単純に
テキスト
のみ、テキストとHTMLの併用
など様々な設定ができるようになっています。
そしてSPAM配信の増加による、迷惑メール対策技術の向上によって、
メールを迷惑メールとして判定させないために、いろいろと気をつける部分となります。新規のシステムならよいのですが、
システムのリニューアルによって、メールが届かなくなるということはよくあります。これは送信されるメールの文面が、以前のシステムと異なっていることも関係します。
例えば、夏休みに明けに、気になるあの子が茶髪ギャルになっていたら、違和感覚えますよね。
迷惑メール判定も、そんな感じです。
夏休み前 夏休み明け ここまでの変化ではないにしても、ちょっと髪型変えたかな?ぐらいの変化、
メールの世界だと、ヘッダーにX-Mailler
が追加されただけでも、迷惑メールとして判定されたりします。感覚的には、文字コード、エンコーディングが違うと大きな変化だったりします。
なので、前回のシステムと同様のメールを送信ができるように、Symfony/mailerも使いこなせる様になる必要があるのです。
テスト環境の構築
Dockerで開発環境を作って、SymfonyのWebスケルトンプロジェクトを作ります。
そこまで作ったのが以下になります。https://github.com/idani/symfony_mailer_tutorial2/tree/de7e20b8af6886a3f1c07927ed5e7a6f3be0801b
一番かんたんな送信方法
「Creating & Sending Messages」のサンプルコードを実行します。
MaillerController.php(その1)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $email = (new Email()) ->from('hello@example.com') ->to('you@example.com') //->cc('cc@example.com') //->bcc('bcc@example.com') //->replyTo('fabien@example.com') //->priority(Email::PRIORITY_HIGH) ->subject('Time for Symfony Mailer!') ->text('Sending emails is fun again!') ->html('<p>See Twig integration for better HTML integration!</p>'); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/7e17f841fc116d0cacab976ec767028c0ce99d49
http://localhost:8000/mailer を開くと以下の画面が表示されてメールが送信されました。
MailDevで確認すると、受信されています。
1つ目の画像はHTMLメールを表示した場合。2つ目の画像は、テキストの画面を表示した場合です。メールのソースは、以下になります。
メールのソースFrom: hello@example.com To: you@example.com Subject: Time for Symfony Mailer! Message-ID: <f9549d7eac6696b8cd513cc398e5bcc9@example.com> MIME-Version: 1.0 Date: Wed, 04 Dec 2019 14:51:44 +0900 Content-Type: multipart/alternative; boundary="_=_symfony_1575438704_55dde3f773e700c11643dd0965c1db31_=_" --_=_symfony_1575438704_55dde3f773e700c11643dd0965c1db31_=_ Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Sending emails is fun again! --_=_symfony_1575438704_55dde3f773e700c11643dd0965c1db31_=_ Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable <p>See Twig integration for better HTML integration!</p> --_=_symfony_1575438704_55dde3f773e700c11643dd0965c1db31_=_--日本語を試してみる
送信者名、受信者名、サブジェクト、本文に日本語を試してみましょう。
メールアドレスでの名前の指定は、Email Addressesに記載がありますように、Address.phpクラスを利用すると良さそうです。
サブジェクト、本文は、何も考えずに日本語を入れてみます。
また、HTMLは面倒なので、ここでは一旦、オミットしておきます。以下のように修正しました。
MailerController.php(その2)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $body = <<<EOL https://symfony.com/why-use-a-framework フレームワークを使用する必要があるのはなぜですか? フレームワークは絶対に必要というわけではありません。それは、あなたがより良く、より速く開発するのを ~~~中略~~~ この点で、フレームワークはブラックボックスではありません!Symfonyの場合、それはまだPHPです...開発されるアプリケーションはSymfonyユニバースに限定されず、たとえば他のPHPライブラリとネイティブに相互運用できます。 EOL; $email = (new Email()) ->from(new Address('hello@example.com', '送信者名')) ->to(new Address('you@example.com', '受信者名')) //->cc('cc@example.com') //->bcc('bcc@example.com') //->replyTo('fabien@example.com') //->priority(Email::PRIORITY_HIGH) ->subject('日本語のサブジェクトになります。長くなると文字化けするという話もありますので、長く書いてみます。これぐらい長いとどうかな?') ->text($body) // ->html('<p>See Twig integration for better HTML integration!</p>') ; $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/91811b3273a7010af25cb132831c4c1a107d64a6
MailDevではこのように受信ができました。
パッと見はよさそうです。メールソースを見ると、quoted-printable でエンコーディングをされていました。
メールソース(日本語)From: =?utf-8?Q?=E9=80=81=E4=BF=A1=E8=80=85=E5=90=8D?= <hello@example.com> To: =?utf-8?Q?=E5=8F=97=E4=BF=A1=E8=80=85=E5=90=8D?= <you@example.com> Subject: =?utf-8?Q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AE?= =?utf-8?Q?=E3=82=B5=E3=83=96=E3=82=B8=E3=82=A7?= =?utf-8?Q?=E3=82=AF=E3=83=88=E3=81=AB=E3=81=AA?= =?utf-8?Q?=E3=82=8A=E3=81=BE=E3=81=99=E3=80=82?= =?utf-8?Q?=E9=95=B7=E3=81=8F=E3=81=AA=E3=82=8B?= =?utf-8?Q?=E3=81=A8=E6=96=87=E5=AD=97=E5=8C=96?= =?utf-8?Q?=E3=81=91=E3=81=99=E3=82=8B=E3=81=A8?= =?utf-8?Q?=E3=81=84=E3=81=86=E8=A9=B1=E3=82=82?= =?utf-8?Q?=E3=81=82=E3=82=8A=E3=81=BE=E3=81=99?= =?utf-8?Q?=E3=81=AE=E3=81=A7=E3=80=81=E9=95=B7?= =?utf-8?Q?=E3=81=8F=E6=9B=B8=E3=81=84=E3=81=A6?= =?utf-8?Q?=E3=81=BF=E3=81=BE=E3=81=99=E3=80=82?= =?utf-8?Q?=E3=81=93=E3=82=8C=E3=81=90=E3=82=89?= =?utf-8?Q?=E3=81=84=E9=95=B7=E3=81=84=E3=81=A8?= =?utf-8?Q?=E3=81=A9=E3=81=86=E3=81=8B=E3=81=AA=EF=BC=9F?= Message-ID: <964a02541e6dc9af98f5af3f31b3bf77@example.com> MIME-Version: 1.0 Date: Wed, 04 Dec 2019 15:22:13 +0900 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable https://symfony.com/why-use-a-framework =E3=83=95=E3=83=AC=E3=83=BC= =E3=83=A0=E3=83=AF=E3=83=BC=E3=82=AF=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99= =E3=82=8B=E5=BF=85=E8=A6=81=E3=81=8C=E3=81=82=E3=82=8B=E3=81=AE=E3=81=AF= =E3=81=AA=E3=81=9C=E3=81=A7=E3=81=99=E3=81=8B=EF=BC=9F =E3=83=95=E3=83= =AC=E3=83=BC=E3=83=A0=E3=83=AF=E3=83=BC=E3=82=AF=E3=81=AF=E7=B5=B6=E5=AF= =BE=E3=81=AB=E5=BF=85=E8=A6=81=E3=81=A8=E3=81=84=E3=81=86=E3=82=8F=E3=81= =91=E3=81=A7=E3=81=AF=E3=81=82=E3=82=8A=E3=81=BE=E3=81=9B=E3=82=93=E3=80= 以下省略Base64で送信する
日本語は送信ができるようですが、エンコーディングが
quoted-printable
なので、いろいろとデータ量が増えています。日本だとbase64が主流なのでbase64
に変更できるかトライしてみます。Emailクラスでメールデータを作成しますので、Email.phpを見ていきます。
そうすると、EmailクラスがMimeコンポーネントに所属していることがわかりました!
メールデータをカスタマイズする
The Mime Componentドキュメントに目を通すと、Emailクラスは高レベルAPI、Messageクラスは低レベルAPIと記載があります。
そして、Creating Raw Email Messagesとして、直接メッセージを作成する方法が記載されていました。
MIMEコンポーネントドキュメント内のサンプルuse Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\Message; use Symfony\Component\Mime\Part\Multipart\AlternativePart; use Symfony\Component\Mime\Part\TextPart; $headers = (new Headers()) ->addMailboxListHeader('From', ['fabien@symfony.com']) ->addMailboxListHeader('To', ['foo@example.com']) ->addTextHeader('Subject', 'Important Notification') ; $textContent = new TextPart('Lorem ipsum...'); $htmlContent = new TextPart('<h1>Lorem ipsum</h1> <p>...</p>', 'html'); $body = new AlternativePart($textContent, $htmlContent); $email = new Message($headers, $body);本文をBase64でエンコードする
サンプルを見ると、メール本文は、TextPartクラスが作成しています。
TextPartクラス
のコンストラクタを見ると、ビンゴです!
エンコーディングを引数に取っていました。/vendor/symfony/mime/Header/AbstractHeader.php(38)public function __construct($body, ?string $charset = 'utf-8', $subtype = 'plain', string $encoding = null) { parent::__construct(); ~~~中略~~~ if (null === $encoding) { $this->encoding = $this->chooseEncoding(); } else { if ('quoted-printable' !== $encoding && 'base64' !== $encoding && '8bit' !== $encoding) { throw new InvalidArgumentException(sprintf('The encoding must be one of "quoted-printable", "base64", or "8bit" ("%s" given).', $encoding)); } $this->encoding = $encoding; } }メールアドレスの名前部分をBase64でエンコードする
ヘッダーは、Headersクラスで定義していますが、エンコーディングについては、基底クラスのAbstractHeaderクラスで、
quoted-printable
に固定されていました。/vendor/symfony/mime/Header/AbstractHeader.phpnamespace Symfony\Component\Mime\Header; use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder; /** * An abstract base MIME Header. * * @author Chris Corbyn */ abstract class AbstractHeader implements HeaderInterface { private static $encoder; ~~~中略~~~ /** * Get a token as an encoded word for safe insertion into headers. */ protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string { if (null === self::$encoder) { ここで固定=> self::$encoder = new QpMimeHeaderEncoder(); } ~~~省略~~~ただし、エンコーディングするのは条件があるようです。
MailboxListHeader.php(106)で、
$this->createPhrase
で名前をエンコードしています。/vendor/symfony/mime/Header/MailboxListHeader.php(106)public function getAddressStrings(): array { $strings = []; foreach ($this->addresses as $address) { $str = $address->getEncodedAddress(); if ($name = $address->getName()) { $str = $this->createPhrase($this, $name, $this->getCharset(), !$strings).' <'.$str.'>'; } $strings[] = $str; } return $strings; }これはAbstractHeader.php(88)で定義されていて、
preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)
にマッチしなければ$phraseStr = $this->encodeWords($header, $string, $usedLength);
でエンコードするようです。/vendor/symfony/mime/Header/AbstractHeader.php(88)protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string { // Treat token as exactly what was given $phraseStr = $string; // If it's not valid if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) { // .. but it is just ascii text, try escaping some characters // and make it a quoted-string if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) { foreach (['\\', '"'] as $char) { $phraseStr = str_replace($char, '\\'.$char, $phraseStr); } $phraseStr = '"'.$phraseStr.'"'; } else { // ... otherwise it needs encoding // Determine space remaining on line if first line if ($shorten) { $usedLength = \strlen($header->getName().': '); } else { $usedLength = 0; } $phraseStr = $this->encodeWords($header, $string, $usedLength); } } return $phraseStr; }
PHRASE_PATTERN
がAbstractHeader.php(23)で定義されていますが、長くて意味がわかりません。たぶん、エンコード済みならスキップする処理じゃないかな?と予想します。
そしたら、名前は、mb_encode_mimeheader
して渡せば良さそうです。/vendor/symfony/mime/Header/AbstractHeader.php(23)const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';サブジェクトをbase64でエンコードする
次にサブジェクトですが、Headers.php(112)からたどっていくと、
AbstractHeaderクラス
を継承したUnstructuredHeaderクラス
に格納されていました。/vendor/symfony/mime/Header/Headers.php(112)/** * @return $this */ public function addTextHeader(string $name, string $value): self { return $this->add(new UnstructuredHeader($name, $value)); }最終的には、UnstructuredHeader.php(65)でエンコードして渡しています。
/vendor/symfony/mime/Header/UnstructuredHeader.php(65)/** * Get the value of this header prepared for rendering. */ public function getBodyAsString(): string { return $this->encodeWords($this, $this->value); }これは
AbstractHeader
で定義されていて、$this->tokenNeedsEncoding
があります。/vendor/symfony/mime/Header/AbstractHeader.php(120)/** * Encode needed word tokens within a string of input. */ protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string { $value = ''; $tokens = $this->getEncodableWordTokens($input); foreach ($tokens as $token) { // See RFC 2822, Sect 2.2 (really 2.2 ??) if ($this->tokenNeedsEncoding($token)) { // Don't encode starting WSP $firstChar = substr($token, 0, 1); switch ($firstChar) { case ' ': case "\t": $value .= $firstChar; $token = substr($token, 1); } if (-1 == $usedLength) { $usedLength = \strlen($header->getName().': ') + \strlen($value); } $value .= $this->getTokenAsEncodedWord($token, $usedLength); } else { $value .= $token; } } return $value; }tokenNeedsEncoding()を確認すると、制御文字と改行が含まれてれば、エンコードが必要という判定をしています。
ですから、こちらもエンコード済みなら、この判定でFalseになって、そのまま使ってくれそうです。
/vendor/symfony/mime/Header/AbstractHeader.php(148)protected function tokenNeedsEncoding(string $token): bool { return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); }サンプルを以下のように書き換えます。
MailerController.php(その3)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $body = <<<EOL https://symfony.com/why-use-a-framework フレームワークを使用する必要があるのはなぜですか? フレームワークは絶対に必要というわけではありません。それは、あなたがより良く、より速く開発するのを ~~~中略~~~ EOL; $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', mb_encode_mimeheader('日本語のサブジェクトになります。長くなると文字化けするという話もありますので、長く書いてみます。これぐらい長いとどうかな?')) ; $textContent = new TextPart($body, 'utf-8', 'plain', 'base64'); $email = new Message($headers, $textContent); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/a3085a095fb71068b5bb856f31c9d9d6ef4d2d9c
これでBase64のいい感じなメールがでるかと思いましたが、MailDevではサブジェクトが文字化けしてしまいました。。。
メールソースを確認すると、
本文は想定通りのUTF-8なのですが、
Subjectは、base64
でエンコードしたデータを渡しているのですが、quoted-printable
で更にエンコードがされていました。また、From/Toは
iso-2022-jp
が使われています。メールソースFrom: =?ISO-2022-JP?B?GyRCQXc/LjxUTD4bKEI=?= <hello@example.com> To: =?ISO-2022-JP?B?GyRCPHU/LjxUTD4bKEI=?= <you@example.com> Subject: =?utf-8?Q?=3D=3FISO-2022-JP=3FB=3FGyRCRnxLXDhsJE4lNSVWJTglJyUv?= =?utf-8?Q?JUgkSyRKJGokXiQ5ISNEOSQvGyhC=3F=3D?= =?utf-8?Q?_=3D=3FISO-2022-JP=3FB=3FGyRCJEokayRISjg7ejI9JDEkOSRrJEgkJCQ?= =?utf-8?Q?mT0MkYiQiJGokXiQ5GyhC=3F=3D?= =?utf-8?Q?_=3D=3FISO-2022-JP=3FB=3FGy?= =?utf-8?Q?RCJE4kRyEiRDkkLz1xJCQkRiRfJF4kOSEjJDMkbCQwJGkkJEQ5GyhC=3F=3D?= =?utf-8?Q??= =?ISO-2022-JP?B?GyRCJCQkSCRJJCYkKyRKISkbKEI=?= Message-ID: <d3e1f390c82ee075dbcf96b7565bccff@example.com> MIME-Version: 1.0 Date: Wed, 04 Dec 2019 16:19:51 +0900 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 aHR0cHM6Ly9zeW1mb255LmNvbS93aHktdXNlLWEtZnJhbWV3b3JrCgrjg5Xjg6zjg7zjg6Djg6/j g7zjgq/jgpLkvb/nlKjjgZnjgovlv4XopoHjgYzjgYLjgovjga7jga/jgarjgZzjgafjgZnjgYvv vJ8K44OV44Os44O844Og44Ov44O844Kv44Gv57W25a++44Gr5b+F6KaB44Go44GE44GG44KP44GR 44Gn44Gv44GC44KK44G+44Gb44KT44CC44Gd44KM44Gv44CB44GC44Gq44Gf44GM44KI44KK6Imv 44GP44CB44KI44KK6YCf44GP6ZaL55m644GZ44KL44Gu44KS5Yqp44GR44KL44Gf44KB44Gr5Yip 55So44Gn44GN44KL44OE44O844Or44Gu44CM44Gh44KH44GG44Gp44CNMeOBpOOBp+OBme+8gQoK 44OV44Os44O844Og44Ov44O844Kv44Gv44CB44OT44K444ON44K544Or44O844Or44Gr5a6M5YWo 44Gr5rqW5oug44GX44CB5qeL6YCg5YyW44GV44KM44CB44Oh44Oz44OG44OK44Oz44K544Go44Ki 44OD44OX44Kw44Os44O844OJ44Gu5Lih5pa544GM5Y+v6IO944Gq44Ki44OX44Oq44Kx44O844K3
iso-2022-jp
については、よくある話で、mb_language("uni")
を宣言すれば良さそうです。mb_language("uni"); mb_internal_encoding("UTF-8");サブジェクトの方で
quoted-printable
になっていますので、
さきほどのAbstractHeader.php(120)のencodeWords()
を更に見ていきましょう。/vendor/symfony/mime/Header/AbstractHeader.php(120)/** * Encode needed word tokens within a string of input. */ protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string { $value = ''; $tokens = $this->getEncodableWordTokens($input); foreach ($tokens as $token) { // See RFC 2822, Sect 2.2 (really 2.2 ??) if ($this->tokenNeedsEncoding($token)) { // Don't encode starting WSP $firstChar = substr($token, 0, 1); switch ($firstChar) { case ' ': case "\t": $value .= $firstChar; $token = substr($token, 1); } if (-1 == $usedLength) { $usedLength = \strlen($header->getName().': ') + \strlen($value); } $value .= $this->getTokenAsEncodedWord($token, $usedLength); } else { $value .= $token; } } return $value; }
$this->getEncodableWordTokens($input)
を見ていなかったので見てみます。/vendor/symfony/mime/Header/AbstractHeader.php(158)protected function getEncodableWordTokens(string $string): array { $tokens = []; $encodedToken = ''; // Split at all whitespace boundaries foreach (preg_split('~(?=[\t ])~', $string) as $token) { if ($this->tokenNeedsEncoding($token)) { $encodedToken .= $token; } else { if (\strlen($encodedToken) > 0) { $tokens[] = $encodedToken; $encodedToken = ''; } $tokens[] = $token; } } if (\strlen($encodedToken)) { $tokens[] = $encodedToken; } return $tokens; }
preg_split('~(?=[\t ])~', $string)
で、=?UTF-8?B?77yf?='
の最後の?=<タブ or スペース>
で分割しています。それを
$this->tokenNeedsEncoding
でエンコード済みかチェックしているのですが。。。サブジェクトを
mb_encode_mimeheader()
した結果は、以下になります。=?UTF-8?B?5pel5pys6Kqe44Gu44K144OW44K444Kn44Kv44OI44Gr44Gq44KK44G+44GZ?= =?UTF-8?B?44CC6ZW344GP44Gq44KL44Go5paH5a2X5YyW44GR44GZ44KL44Go44GE44GG?= =?UTF-8?B?6Kmx44KC44GC44KK44G+44GZ44Gu44Gn44CB6ZW344GP5pu444GE44Gm44G/?= =?UTF-8?B?44G+44GZ44CC44GT44KM44GQ44KJ44GE6ZW344GE44Go44Gp44GG44GL44Gq?= =?UTF-8?B?77yf?='さきほどの、
preg_split('~(?=[\t ])~', $string)
で分割をすると、分割したデータの最後に改行がついてしまうみたいで、その後の$this->tokenNeedsEncoding($token)
でエンコード済みと判定されないようです。それでは、
mb_encode_mimeheader()
の結果から改行コードを外してみます。サンプルコードを以下のように修正しました。
MailerController.php(その4)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $body = <<<EOL https://symfony.com/why-use-a-framework フレームワークを使用する必要があるのはなぜですか? フレームワークは絶対に必要というわけではありません。それは、あなたがより良く、より速く開発するのを ~~~中略~~~ EOL; mb_language("uni"); mb_internal_encoding("UTF-8"); $subject = mb_encode_mimeheader('日本語のサブジェクトになります。長くなると文字化けするという話もありますので、長く書いてみます。これぐらい長いとどうかな?'); $subject = str_replace("\r\n", '', $subject); $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', $subject) ; $textContent = new TextPart($body, 'utf-8', 'plain', 'base64'); $email = new Message($headers, $textContent); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/71283d6216dcc3f4e1b710a0d2dd452ca63a87e9
実行してみると、subjcetの文字化けが治りましたね。
メールソースを確認しましたが、想定どおりです。
メールソースFrom: =?UTF-8?B?6YCB5L+h6ICF5ZCN?= <hello@example.com> To: =?UTF-8?B?5Y+X5L+h6ICF5ZCN?= <you@example.com> Subject: =?UTF-8?B?5pel5pys6Kqe44Gu44K144OW44K444Kn44Kv44OI44Gr44Gq44KK44G+44GZ?= =?UTF-8?B?44CC6ZW344GP44Gq44KL44Go5paH5a2X5YyW44GR44GZ44KL44Go44GE44GG?= =?UTF-8?B?6Kmx44KC44GC44KK44G+44GZ44Gu44Gn44CB6ZW344GP5pu444GE44Gm44G/?= =?UTF-8?B?44G+44GZ44CC44GT44KM44GQ44KJ44GE6ZW344GE44Go44Gp44GG44GL44Gq?= =?UTF-8?B?77yf?= Message-ID: <1916f37d03fc486c2babe15a606eda8f@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 11:19:48 +0900 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 aHR0cHM6Ly9zeW1mb255LmNvbS93aHktdXNlLWEtZnJhbWV3b3JrCgrjg5Xjg6zjg7zjg6Djg6/j g7zjgq/jgpLkvb/nlKjjgZnjgovlv4XopoHjgYzjgYLjgovjga7jga/jgarjgZzjgafjgZnjgYvv vJ8K44OV44Os44O844Og44Ov44O844Kv44Gv57W25a++44Gr5b+F6KaB44Go44GE44GG44KP44GR 44Gn44Gv44GC44KK44G+44Gb44KT44CC44Gd44KM44Gv44CB44GC44Gq44Gf44GM44KI44KK6Imv 以下省略base64のまとめ
ということで、サブジェクトのワークアラウンドが微妙ですが、UTF-8+base64での送信が実現できました。
パッチを送る感じになるのかな?
iso-2022-jpで送信する
それでは、文字コードが
iso-2022-jp
で、エンコーディングが7bit
にチャレンジしてみます。ヘッダーは、
mb_encode_mimeheader()
で準備ができますし、本文が7bitが通れば行けそうです。サンプルコードを以下のように修正します。
MailerController.php(その5)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $body = <<<EOL https://symfony.com/why-use-a-framework フレームワークを使用する必要があるのはなぜですか? フレームワークは絶対に必要というわけではありません。それは、あなたがより良く、より速く開発するのを ~~~中略~~~ EOL; $subject = mb_encode_mimeheader('日本語のサブジェクトになります。長くなると文字化けするという話もありますので、長く書いてみます。これぐらい長いとどうかな?'); $subject = str_replace("\r\n", '', $subject); $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', $subject) ; $textContent = new TextPart($body, 'iso-2022-jp', 'plain', '7bit'); $email = new Message($headers, $textContent); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/5bc62be13f645e0f0554dabebb8ce7bd095fc6ad
以下のようなエラーがでました。
7bit
のエンコーディングに対応していないようです。しょうがないので、エンコーディングを
base64
に変更してトライしてみます。MailerController.php(その6)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $body = <<<EOL https://symfony.com/why-use-a-framework フレームワークを使用する必要があるのはなぜですか? フレームワークは絶対に必要というわけではありません。それは、あなたがより良く、より速く開発するのを ~~~中略~~~ EOL; $subject = mb_encode_mimeheader('日本語のサブジェクトになります。長くなると文字化けするという話もありますので、長く書いてみます。これぐらい長いとどうかな?'); $subject = str_replace("\r\n", '', $subject); $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', $subject) ; $textContent = new TextPart($body, 'iso-2022-jp', 'plain', 'base64'); $email = new Message($headers, $textContent); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/71f73ca030d516c4e9ff95521798f7b65344dbd1
本文が痛い感じになってしまいました。
こちらがメールソースになります。From/To/Subjectのヘッダーは、想定通りですし、
Content-Type
も想定どおりです。メールソースFrom: =?ISO-2022-JP?B?GyRCQXc/LjxUTD4bKEI=?= <hello@example.com> To: =?ISO-2022-JP?B?GyRCPHU/LjxUTD4bKEI=?= <you@example.com> Subject: =?ISO-2022-JP?B?GyRCRnxLXDhsJE4lNSVWJTglJyUvJUgkSyRKJGokXiQ5ISNEOSQvGyhC?= =?ISO-2022-JP?B?GyRCJEokayRISjg7ejI9JDEkOSRrJEgkJCQmT0MkYiQiJGokXiQ5GyhC?= =?ISO-2022-JP?B?GyRCJE4kRyEiRDkkLz1xJCQkRiRfJF4kOSEjJDMkbCQwJGkkJEQ5GyhC?= =?ISO-2022-JP?B?GyRCJCQkSCRJJCYkKyRKISkbKEI=?= Message-ID: <e2e57a249f7daeaff165d25eddc2a876@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 12:20:15 +0900 Content-Type: text/plain; charset=iso-2022-jp Content-Transfer-Encoding: base64 aHR0cHM6Ly9zeW1mb255LmNvbS93aHktdXNlLWEtZnJhbWV3b3JrCgrjg5Xjg6zjg7zjg6Djg6/j g7zjgq/jgpLkvb/nlKjjgZnjgovlv4XopoHjgYzjgYLjgovjga7jga/jgarjgZzjgafjgZnjgYvv vJ8K44OV44Os44O844Og44Ov44O844Kv44Gv57W25a++44Gr5b+F6KaB44Go44GE44GG44KP44GR 44Gn44Gv44GC44KK44G+44Gb44KT44CC44Gd44KM44Gv44CB44GC44Gq44Gf44GM44KI44KK6Imv 44GP44CB44KI44KK6YCf44GP6ZaL55m644GZ44KL44Gu44KS5Yqp44GR44KL44Gf44KB44Gr5Yip 55So44Gn44GN44KL44OE44O844Or44Gu44CM44Gh44KH44GG44Gp44CNMeOBpOOBp+OBme+8gQoK 以下、省略またまた、ソースコードを見てみると、
TextPartクラス
で渡しているiso-2022-jp
というのは、Content-Type
の出力に使うだけで、文字コードの変換はしていませんでした。一応、Base64Encoder.php(26)で
$charset
として文字コードまでもらっているのですが、使っていません;;/vendor/symfony/mime/Encoder/Base64Encoder.php(26)class Base64Encoder implements EncoderInterface { public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string { if (0 >= $maxLineLength || 76 < $maxLineLength) { $maxLineLength = 76; } $encodedString = base64_encode($string); $firstLine = ''; if (0 !== $firstLineOffset) { $firstLine = substr($encodedString, 0, $maxLineLength - $firstLineOffset)."\r\n"; $encodedString = substr($encodedString, $maxLineLength - $firstLineOffset); } return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n")); } }そしたら本文も自前で変換する必要がわかったので、以下のようにします。
MailerController.php(その7)class MailerController extends AbstractController { /** * @Route("/mailer", name="mailer") */ public function index(MailerInterface $mailer) { $body = <<<EOL https://symfony.com/why-use-a-framework フレームワークを使用する必要があるのはなぜですか? フレームワークは絶対に必要というわけではありません。それは、あなたがより良く、より速く開発するのを ~~~中略~~~ EOL; $subject = mb_encode_mimeheader('日本語のサブジェクトになります。長くなると文字化けするという話もありますので、長く書いてみます。これぐらい長いとどうかな?'); $subject = str_replace("\r\n", '', $subject); $headers = (new Headers()) ->addMailboxListHeader('From', [new Address('hello@example.com', mb_encode_mimeheader('送信者名'))]) ->addMailboxListHeader('To', [new Address('you@example.com', mb_encode_mimeheader('受信者名'))]) ->addTextHeader('Subject', $subject) ; $body = mb_convert_encoding($body, 'ISO-2022-JP-MS', 'UTF-8'); $textContent = new TextPart($body, 'iso-2022-jp', 'plain', 'base64'); $email = new Message($headers, $textContent); $mailer->send($email); return $this->render('mailer/index.html.twig', [ 'controller_name' => 'MailerController', ]); } }https://github.com/idani/symfony_mailer_tutorial2/commit/dd161129fdcf0ba36bfb93e6461b67f03eac6b59
本文も問題なさそうです。
メールソースはこんな感じ。
メールソースFrom: =?ISO-2022-JP?B?GyRCQXc/LjxUTD4bKEI=?= <hello@example.com> To: =?ISO-2022-JP?B?GyRCPHU/LjxUTD4bKEI=?= <you@example.com> Subject: =?ISO-2022-JP?B?GyRCRnxLXDhsJE4lNSVWJTglJyUvJUgkSyRKJGokXiQ5ISNEOSQvGyhC?= =?ISO-2022-JP?B?GyRCJEokayRISjg7ejI9JDEkOSRrJEgkJCQmT0MkYiQiJGokXiQ5GyhC?= =?ISO-2022-JP?B?GyRCJE4kRyEiRDkkLz1xJCQkRiRfJF4kOSEjJDMkbCQwJGkkJEQ5GyhC?= =?ISO-2022-JP?B?GyRCJCQkSCRJJCYkKyRKISkbKEI=?= Message-ID: <e9e0eefd92fd68df3ec3a8bf6133bf9b@example.com> MIME-Version: 1.0 Date: Thu, 05 Dec 2019 12:36:00 +0900 Content-Type: text/plain; charset=iso-2022-jp Content-Transfer-Encoding: base64 aHR0cHM6Ly9zeW1mb255LmNvbS93aHktdXNlLWEtZnJhbWV3b3JrCgobJEIlVSVsITwlYCVvITwl LyRyO0hNUSQ5JGtJLE1XJCwkIiRrJE4kTyRKJDwkRyQ5JCshKRsoQgobJEIlVSVsITwlYCVvITwl LyRPQGRCUCRLSSxNVyRIJCQkJiRvJDEkRyRPJCIkaiReJDskcyEjJD0kbCRPISIkIiRKJD8kLCRo JGpOSSQvISIkaCRqQi4kLzMrSC8kOSRrJE4kcj11JDEkayQ/JGEkS014TVEkRyQtJGslRCE8JWsk TiFWJEEkZyQmJEkhVxsoQjEbJEIkRCRHJDkhKhsoQgoKGyRCJVUlbCE8JWAlbyE8JS8kTyEiJVMl OCVNJTklayE8JWskSzQwQTQkSz1gNXIkNyEiOT1CJDI9JDUkbCEiJWElcyVGJUolcyU5JEglIiVD JVclMCVsITwlSSROTj5KfSQsMkRHPSRKJSIlVyVqJTEhPCU3JWclcyRyMytILyQ3JEYkJCRrJEgk JCQmM048QkAtJHJEczYhJDkkayQ/JGEkRyQ5ISMbKEIKChskQjMrSC88VCQsSEZNUSViJTglZSE8 JWskcjpGTXhNUSQ3JEZCPiROTk4waCRLPThDZiQ5JGskMyRIJEc7fjRWJHJAYUxzJEckLSRrJD8k 以下省略iso-2022-jpのまとめ
メール本文に
7bit
エンコーディングが使えなかった。
base64
でエンコーディングするなら、迷惑メール判定としては、大きく変更と感じる可能性があります。つまり迷惑メールに初回は入る可能性があります。
それならISO-2022-jpを捨てて、utf-8にしても結局迷惑メールになりますので大差ありません。
UTF-8だと、顔文字など使える文字の種類が増えるメリットがありますから、UTF-8に乗り換えがよさそうです。
個人的なまとめ
symfony/mailerを使う場合は、システム刷新として使うなら、
utf-8
の文字コード、base64
エンコーディングを使うのが良さそうです。新規システムなら、エンコーディングが
quoted-printable
になりますが、簡単に使えるのも魅力的だと思います。
- 投稿日:2019-12-05T11:36:49+09:00
LravelのLogLevelを環境変数で変更したい (6.0LTS)
タイトルの通り Laravel6.0LTS にて、ログレベルを環境変数で変更するための手順です。
チャンネルやその他の項目も含めてログに関するいろいろな設定については他に詳しい記事がありますのでそちらをご参照ください。
Laravel5.6 での ログ設定についてここでは、そこまで複雑なことがしたいわけではなく、シンプルに、開発環境ではDEBUGまで、ステージ、本番環境ではINFO以上でログを出力するのを、環境変数で調整したい、というやり方です。
.env に変数を追加
適当な値でいいですが、
project_root/.env
ファイルにLEVELを指定するための変数を追加します。
ここではLOG_LEVEL
としました。LOG_LEVEL=info
config/logging.php
を修正ファイルの中身を見ると以下のようになっているかと思います。
(コメント部分と後半は省略)'default' => env('LOG_CHANNEL', 'stack'), 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['daily'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', 'days' => 14, ], ...上の例では、
default=>stack
が指定されており、stackの中でchannels=>daily
が指定されています。
そしてdailyのlevel=>debug
になっているため、DEBUG以上のログが出力されることになります。そこで、この daily=>levelの部分を以下のように書き換えます。
'level' => env('LOG_LEVEL','debug'),あとは環境に合わせて、先程
.env
に追加したLOG_LEVEL
の値を変更してやれば、環境ごとに合わせたログレベルでログが出力されます。ログレベルの一覧は PSR-3でこのように定義されていて 記載されている順番の並びで、指定したレベルより上に書かれている内容が出力されることになります。
Laravelのバージョンいくつから使えるのかまでは調査していません(4.2と5.1のあと6.0に飛んだだめ…)が、
config/logging.php
の中身が同じような状態であれば使える方法かと思います。
なにか他にもっと簡単な切り替え方があるような気はするんですが、
.env
にはじめから設定されている値はLOG_CHANNEL
だけで、別にチャンネル切り替えるつもりは無いんだけど、レベルだけ切り替えるやり方というのが見当たらなかったのでやってみました。
「本来はこうするべき」というやり方がありましたら教えていただけると嬉しいです。
- 投稿日:2019-12-05T10:08:16+09:00
【Laravel】routes.phpでDBから値を取得する
routes.phpからレコードの値を取ってきてそのままviewへ表示させたい、という部分があったので、メモ程度に書きます。
環境
PHP : 5.6.30
MySQL : 10.1.21
Laravel : 5.1.46テーブル
以下サンプルテーブルです。
wordsテーブル
id message 1 Hello 2 こんにちは コード
route.phpRoute::get('/', function () { // cookieからidを設定する if ($_COOKIE['lang'] == 'en') { $id = 1; } else { $id = 2; } // DB::table()を使用して値を取得する $query = DB::table('words') ->select('message') ->where('id', $id) ->first(); $message = $query->message return view('main',['message' => $message]); }main.blade.php<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> {{$message}} </body> </html>bladeの方はおまけ的な感じですが、一応念のため。
- 投稿日:2019-12-05T09:10:24+09:00
社内基幹システムのバージョンアップに挑んだ話
こちらは Fusic Advent Calendar 2019 - Qiita 5日目の記事です。
昨日は @NaoyaMiyagawa によるS3オブジェクトレベルのログ記録+監視方法 でした。
ログと監視は大事ですよね~。ということで、こんにちは。早﨑です。
毎度のことですが時間の流れが速く、気づけばもう年の瀬ですね。
1年を振り返ると今年も業務システムをガッツリやってました。早速ですが今年は弊社の基幹システムのバージョンアップをした話を書こうと思います。
弊社の基幹システムとは?
ざっくり機能を上げると
- お取引をさせていただいてる企業・案件・見積・請求・入金データの管理機能
- 社員一人一人の工数管理機能
- 各種集計出力機能
- 社内回覧機能
- 社員管理機能
- 各種マスタ
等々を持ったシステムです。
これらCakePHP3の出た当初に作成しました。(4年くらい前)
PHPのバージョンも古く5.6
JSもjqueryで。。。上げ始めたらキリがないです
CakePHP3が出たはじめの頃でEntityとは何ぞや?右も左も良く分からないまま実装したためカオスなコードを書いています。
テストコードもホボ無いという状況です。一念発起してやった
その前に何から手を付けようかと考えました。
思いつきベースでやりたいことを貯めていたのですが、全部は無理。
社内から遅いと声が上がっていたので、まずは最低限のPHPとCakePHPを上げようということで社内各方面で相談しながら何をやるかを決めました。最終的には、PHPとCakePHPのバージョンを上げる。となりました。
- PHP5.6 → 7.3系へ
- CakePHP3.1 → 3.8系へ
PHPはサポートも切れてますし、速度面でもPHP7には劣ります。
CakePHP4を見据えて最新化することを目指しましょうという感じですね。ということで次にどういう手順でやるかを考えました。
順番
- 1.開発環境をPHP7.3系で構築(本番環境も併せて検討)
- 2.システムを設置しとりあえず動くところまで持っていく
- 3.非推奨関数の置換
- 4.リファクタ
- 5.テスト
- 6.本番リリース
やってきます
1.ローカルにPHP7.3系の環境を構築(本番環境も併せて検討)
対象のシステムはdocker環境で運用されています。
しかし私のローカルはvagrant、本番では基幹システム用のimageを作ることが必須となるのでこれを期に開発環境もdockerにしました。
(docker難しい。。。)2.システムを設置しとりあえず動くところまで持っていく
git cloneしcomposer install!が動きません。
composerで入るプラグインが入りませんw
PHPのバージョンが新しくプラグインのバージョンを上げてくださいと言われました。
上げてプラグインをインストール。で、ブラウザを叩きます。
画面はホワイトアウトです。感の良い方は分かると思いますがCakePHP3.3からMiddlewareが導入されています。
ディレクトリやファイルの中身が若干変わっています。
Cake3.8系をcloneしWinMergeを使いながら3.1と比較しながら書き直していきました。修正してはdumpを吐いてどこまで来ているかのチェックの繰り返しです。
かなり骨が折れました。3.非推奨関数の置換
CakePHP3.8に上げたことでScrutinizerがあらぶり始めました。
↑のIssueは殆どがDeprecatedでした
動作としては非推奨を無視するように設定していたのでブラウザからのアクセスには問題は無いのですが
はやりここはきっちりと対応しておくべきでしょう。4も控えていますし。ということで以下を変更しエラーを全て出すようにしました。
app.php'Error' => [ // 'errorLevel' => E_ALL & ~E_USER_DEPRECATED, // ← ここを 'errorLevel' => E_ALL, // ← こうする 'exceptionRenderer' => ExceptionRenderer::class, 'skipLog' => [], 'log' => true, 'trace' => true, ],これでエラーが見れるようになります。
殆どgetter/setter(笑)画面をポチポチしながら出てきたもの片っ端から対応します。
4.リファクタ
3をしながら思うわけです。
俺、なんでこんな残念なコード書いてたんだろう。。。
と
複雑な部分は一旦さておき、パッと見で修正できるレベルのものを書き換えていきます。
折角なのでtypehintも入れていきます。完全に作業の世界です。
併せてコードはcommit時にphp-cs-fixer/phpcbf/phpmdが走るようにしたので静的解析チェックをしていきます。
Scrutinizerがあったのも良かったですね。併せてチェックができて良かったです。
template(ctp)以外の全てのコードに対して修正を行えたので凄く綺麗になりました。5.テスト
最後にテストコードを書きたい!!
と思ってましたがこの時点では出来ませんでした。
そこまでモチベーションが続かなかったです。画面ポチポチで確認していきます。
そんなに複雑ではないから大丈夫だろうと思ってたけど、やっぱり漏れはありますね。
本番リリース後にやはり動かないんだけど。。。という事がありました。テストコードで100%安心できるわけでは無いですが、これにより気づける事もあります。
少しでも安心できる状況を作るのが大事ですね。6.本番リリース
インフラ担当者と日時、手順を相談してえいっとやりました。
特に問題は無かったです。事前に手順を相談し認識を合わせたのが良かったと思います。
まとめ
- 協力者を得つつ、やりたいこと・やるべきこと・やらないことを整理することが大事。
- コードのリファクタはちょっと踏み込み過ぎてどこまでやるか悩む。
- テストは大事
- バグは出ます。弊社基幹システムでこのシステムがバグってしまうと仕事が出来なくなります(特にバックオフィス)
- 軽微なバグで直ぐ対応出来たもののやはりお客様のシステムだとNGですよね。
- バージョンアップは大変
- 動かないところが多すぎて心が折れそうになる。
- 業務の合間や盆休みを使用して行っているため数か月掛かった。
最後に
社内基幹システムのPHPとフレームワークのバージョンを上げられて満足満足
— tsukabo (@tsukabo) August 8, 2019社内からも速くなったねーとお言葉をいただけましたし、勉強にもなりました。
やってよかったです。さあ次はテストコードを書かねば、俺達の戦いは始まったばかりだ
それでは、明日のアドベントカレンダーは @ya_ma23 です!
お楽しみに!
- 投稿日:2019-12-05T00:01:54+09:00
Laravel コントローラーからビューを表示
ビューの作成
resources/views
にindex.blade.phpファイルを作成する。
書き方に関しては下記参照
https://blog.hiroyuki90.com/articles/laravel-blade///変数を展開する場合 <h1>{{ $hello }}</h1>コントローラーの作成
app/Http/Controllers
にコントローラーを作成する。<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\View; class HomeController extends Controller { public function index() { $hello = 'Hello World'; return view('index', ['hello' => $hello]); //1つ目の引数にディレクトリとファイル名指定 //2つ目の引数でビューに$hello変数を渡す } }viewヘルパを使用する際は
use Illuminate\Support\Facades\View;
を記載しないとエラーが出てしまう。
view
ヘルパの中でresources/views
にサブディレクトリを作成する際はドット記法を使い記述する。return view('admin.index', ['hello' => $hello]);1つ目の引数の
index
の部分がblade.phpのファイル名
2つ目の引数のhello
の部分がblade.phpファイルの中の変数を指定
$hello
がビューに渡す変数