Top page  1/38
06
2019

LaravelでCache-Controlヘッダーを設定する

CATEGORYPHP
Laravelでは、レスポンスのCache-Controlヘッダーがデフォルトでは no-cache, private で返るみたいなんだけど、キャッシュしてもいいAPIでは設定変えたかったのでその方法。

ググると自前でミドルウェア作って~みたいな情報も出てくるけど、結論からいうとLaravel 5.6からSetCacheHeadersというミドルウェアが標準で用意されているので、今はこれを↓みたいに有効にするだけでよい。
Route::middleware('cache.headers:public')->group(function () {
Route::get('news', 'NewsController@index');
});
最終的に呼ばれるのはResponse::setCache()。オプションの解説は見当たらなかったが、publicとかetagとかを指定するとそれが有効になるようだ。

Tag: PHP Laravel

04
2019

LaravelでPaginatorをカスタムクラスに差し替える

CATEGORYPHP
またLaravelネタ。Laravel 5.7でJSON APIを作るにあたって、標準のLengthAwarePaginatorをそのまま使うと余計なプロパティが返ってしまうのでオーバーライドしたかった。最初はModel::paginate()をオーバーライドしようかと思ったけど、そんなことせずとも以下のようにDIで簡単に出来た。
namespace App\Models;

use Illuminate\Pagination\LengthAwarePaginator;

class JsonLengthAwarePaginator extends LengthAwarePaginator
{
public function toArray() : array
{
return [
'currentPage' => $this->currentPage(),
'data' => $this->items->toArray(),
'from' => $this->firstItem(),
'lastPage' => $this->lastPage(),
'perPage' => $this->perPage(),
'to' => $this->lastItem(),
'total' => $this->total(),
];
}
}
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
public function boot() : void
{
}

public function register() : void
{
$this->app->bind(
'Illuminate\Pagination\LengthAwarePaginator',
'App\Models\JsonLengthAwarePaginator'
);
}
}
paginate()でLengthAwarePaginatorインスタンスを生成してるのはBuildsQueries::paginator()だったけど、こいつが親切にもDIを使ってインスタンスを作っているので、設定を書き換えるだけでいけた。
なお、simplePaginate()の場合はPaginatorクラスが返るので、必要ならそちらも設定忘れずに。

Tag: PHP Laravel

25
2019

Laravelで日時をISO 8601形式にするとめんどくさい

CATEGORYPHP
APIで返す日時のフォーマットというとUNIXTIMEISO 8601形式(2019-01-26T12:00:00Z みたいな奴)だと思うが、Laravel 5.7でISO 8601形式を使おうとしたら、実現はできたけど色々ハマったのでメモ。

Laravelでの日時の扱い

まず、Laravelの日時の扱いを整理する。

Laravelでは、日時を扱うクラスとしてPHP標準のDateTimeではなく、DateTimeを拡張したCarbonライブラリを用いる。

テーブルの列がDATETIME等でも、初期状態では文字列として扱われてしまう。
モデルに $dates で日付ミューテタというものを設定できるので、そこに日付として扱う列名を指定する。
(自動生成の created_at, updated_at はデフォルトで入っている。deleted_at は入っていない。deleted_at は追加しないと文字列だったりインスタンスだったりと変な動作をする。)

そうすると、$user->last_login みたいに参照した際にCarbonインスタンスが返ってくる。
APIでJSONとして返したりする際は、Carbonインスタンスではなく、日時文字列に変換されて返る。

JSON変換時のフォーマット

JSON変換の際のフォーマットは、デフォルトでは Y-m-d H:i:s になる模様(環境によって違うかも?)。

フォーマットを指定する方法は複数存在する。
  1. $casts'datetime:' . \DateTime::ATOM のように個別の列ごとに指定する。
  2. $dateFormat でモデルごとに指定する
  3. Carbon::serializeUsing() でグローバルに指定する。
結論から言うと、実際にISO 8601形式を指定して動作したのは1の $casts だけだった。かつ、その場合も後述のように別途対応が必要だった。

問題点1) Carbon::serializeUsing() は toJson() では機能しない

ドキュメントだと「アプリケーション上のすべての日付と時刻が」とか書かれていてこれ一つで対応できそうにみえる Carbon::serializeUsing() だが、Laravel 5.7では実際には機能しない
いや厳密には、Carbonインスタンス単体をシリアライズするケースでは機能するのだが、モデルインスタンスをシリアライズする際に呼ばれる toJson() では、モデル用の変換処理が独自に文字列にしてしまうらしく、機能しない。

これは、Issueとして報告されているようなので、将来のバージョンではバグとして修正される可能性もある。

ただし、Carbon::serializeUsing() は影響範囲が非常に広く、ここでISO 8601に変換してしまうと、Eloquent等でCarbonインスタンスを渡した際も全部ISO 8601形式になって、SQLがエラーになったりするので、いずれにせよおススメしない。
ここは、DBとかに合わせて Y-m-d H:i:s 辺りにしとくのがよさそう。

問題点2) $dateFormat はDB周りでエラーになる

ということで、じゃあモデルに $dateFormat = \DateTime::ATOM を指定して、モデル内のデフォルトフォーマットをISO 8601形式にしよう、とするわけなのだが、これもうまくいかない。
具体的には、モデルの asDateTime() メソッドというのが、DBから取得した Y-m-d H:i:s の日時文字列をISO 8601としてパースしようとしてエラーになっていた。
設定ミス?Laravelのバグ?これは出来てよさそうな気がするのだが…。

なお、後述のように asDateTime() をオーバーライドして修正しても、問題点1と同じくEloquentに渡す日時も全部ISO 8601形式になるので結局駄目だった。

問題点3) asDateTime() はISO 8601形式の日時をパースできない

上で出てきた asDateTime() メソッドは、モデルに値を設定した際も呼ばれるのだが、結局ISO 8601形式で文字列を渡したところでパースできないっぽい???
上でISO 8601としてパースしようとしてエラーになるって言ったくせに何だよって感じだが、$casts でフォーマットを指定してやっても、今度はISO 8601を Y-m-d H:i:s としてパースしようとしてエラーになるっていう。なんだこれ。。。

解決策

で、じゃあどうすればいいのかというと、最終的に以下の結論にたどり着いた。
  1. asDateTime() をオーバライドして、ISO 8601形式をパース出来るようにする
  2. ISO 8601形式にする列全てに 'datetime:' . \DateTime::ATOM を指定する
Carbon自体はもちろんISO 8601形式に対応している(new Carbon('2019-01-26T12:00:00Z') でよい)ので、 asDateTime() にパースを追加するの特に難しい話ではない。
datetime でフォーマットを指定するのも、冗長ではあるもののモデルの設定だけで済む話なので、一番手軽に対応できる。
(後者は、面倒なら toArray() とかをオーバーライドして一括でやってもいいけど。)

他にも setLastLoginAttribute() みたいなミューテタで個別に対応する方法もあるが、モデル数が増えると現実的ではないと思われる。
ということで、この辺の対応をおススメしておく。

その他の選択肢

ただし、そもそもUNIXTIMEにするとか他の解決策もあるので、ISO 8601に拘る必要がないなら、そうした方が安全かもしれない。

UNIXTIMEだと、同じく $dateFormat の設定は必要そうだが、それ以外は特に問題なさそうな雰囲気。
ただし、Carbonインスタンスの代わりにintが返ってくるようなので、そこだけご注意を。

Tag: PHP Laravel

27
2018

Laravel + Vagrant + VirtualBox shared folders がクソ遅い問題

CATEGORY開発環境
なんかLaravelが遅いと思ったら、表題の組み合わせだとクソ遅いという話。
(以下、環境はLaravel 5.7, Vagrant 2.2.2, VirtualBox 5.2.22。)

Vagrantの共有フォルダとして、ホスト側の準備が楽なvirtualbox(VirtualBox shared foldersを使う共有方式)を愛用しているのだけど、Laravelプロジェクトで使っていたら妙に遅い。
空っぽのAPIで2秒とかかかるレベル。遅すぎる。
最初Laravelやnginx、DBの設定ミスかと思ってログ入れたりして調べてみたけど、いずれも遅くない。
っていうか、nginxからLaravelに来るまでが遅い?環境?
と思ってググってたらこんな話を見つけました。
今回はHomestead使ってないけど、症状的にはこれでした。
という事で、掲載されているvagrant-winnfsdプラグインを入れ、共有方式をnfsにして、VMを作り直して再計測。
無事、2秒→600ミリ秒レベルまで高速化されました。
サンプルこれ。ちなみに、NFSもやめてローカルファイルにすると200ミリ秒レベルになりました。)

VirtualBox Shared Foldersは遅い遅いと言われているけど、Laravelだとファイル数が多くて毎リクエスト読み込むせい(?)か、クソ遅くなってしまう模様。
vagrant-winnfsdプラグインを使えば、特に複雑な設定も必要なくNFSに出来たので、VagrantでPHP環境作る時はこっちを常用した方がよさそう。

Tag: PHP Vagrant Laravel

23
2018

Firebaseの使い方メモ(Unity初級)

CATEGORYUnity
お仕事のUnity開発でGoogleのmBaaSFirebaseをちょっと使ったので、調べた初歩的な話をメモ。
(主にRealtime Database周り。)

Firebaseとは

Googleのモバイルアプリ向けのバックエンド (mBaaS)。
モバイルアプリで必要な各種機能を備える。主な機能は以下。

Firebase Authentication
ユーザー認証。Twitter認証などの他、匿名の端末認証も備える。
Firebase Realtime Database
NoSQL DB。JSONをそのまま扱う。簡単な検索や、キーごとのアクセス権の管理などもできる。
Firebase Hosting
ホスティングサービス。WebViewやコンテンツ配信などが出来そう。
Cloud Storage
ストレージサービス。ユーザーが投稿した画像などをアップロードできるらしい。
Cloud Functions for Firebase
FaaS的な機能。Node.jsでサーバーロジックを作る事が出来る。
Googleアナリティクス
Web全般でよく使う分析ツール。いろいろ統合されているらしい。

Tag: Firebase Unity