Composerの高速化を設定した状態で2系に上げるとトラブる
https://github.com/hirak/prestissimo プラグインを入れたり、https://packagist.jp/ ミラーを使うように設定した状態で、Composerを2系にアップデートすると、composer require
時等に以下のような警告・エラーが出る。
The "hirak/prestissimo" plugin was skipped because it requires a Plugin API version ("^1.0.0") that does not match your Composer installation ("2.0.0"). You may need to run composer update with the "--no-plugins" option.
これはとりあえず--no-plugins
オプションをつけて実行することでも対処できるが、多分ほかのプラグインも動かないし、アンインストールしてしまおう。
composer global remove hirak/prestissimo
Composer 2系ではprestissimoがなくても、並列ダウンロードしてくれるようでだいぶ高速になっている。
また、
[InvalidArgumentException] Could not find a version of package livewire/livewire matching your minimum-stability (dev). Require it with an explicit version constr aint allowing its desired stability.
あるいは、
[InvalidArgumentException] Could not find package livewire/livewire.
このようなエラーが出る場合は、ミラーの https://packagist.jp/ が2系用のパッケージを持っていないせいのようだ。エラーメッセージもうちょっとなんとかならないのかとは思うが。
packagist.jpのウェブページに書いてある通り、
composer config -g --unset repos.packagist
すればおけ。prestissimoが多分いらないかなという状況なのに対して、ミラーはまだある程度速度に効いてそう、つまり2系Composeでも地理的なネックがあるような気はするので、もしpackagist.jpが2系に対応してくれるならこれは戻すのもありかも。
Laravelのファサードとはなにか
ずっと使っているけど十分に理解できなかったので調べた。
ファサードとはなにか
結論から言うと、ファサードは、対応したサービスのインスタンスのメソッドを、静的にコールできる仕組み、のようだ。
GoFのデザインパターンのFacadeパターンとはちょっと違う。あれは複数の複雑な機能に対する一つのアクセス方法を提供するものだったと思うので。こっちはもっとシンプル。多少の例外はあるが、基本的にはサービスに委譲するだけだ。
Illuminate\Support\Facades\Request
ファサード(以下Requestファサード)を例に挙げる。このファサードは'request'
エイリアスのサービスと対応していて、その実体はIlluminate\Http\Request
のインスタンスである。
コントローラアクションで、Illuminate\Http\Request
をDIして持って来た$request
のたとえば$request->all()
と、ファサードのRequest::all()
は同等になる。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request as HttpRequest; use Illuminate\Support\Facades\Request; class TestController extends Controller { public function index(HttpRequest $request) { dd($request->all(), Request::all()); # 等しい } }
ファサードはどう実装されているか
まずファサードはクラスである。Illuminate\Support\Facades
名前空間以下にあるクラスで、Illuminate\Support\Facades\Facade
抽象クラスを継承している。
ファサードクラス内にはgetFacadeAccessor()
というメソッドが共通して存在していて、これが肝になっている。単純にこの戻り値によって対応するサービスが決まっている。
大抵文字列で、サービスのエイリアスが返されているが、Schemaファサードに関してはオブジェクトが直接返されていたりする。
オブジェクトの場合はそのまま使うだけだが、サービスのエイリアス等の場合は、必要に応じてresolve()
相当の解決が行われている。
つまりRequestファサードで静的にメソッドを呼び出すのと、resolve('request')
されたオブジェクトのメソッドを呼び出すのも同じことになる。
<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Request; class TestController extends Controller { public function index() { dd(resolve('request')->all(), Request::all()); # 等しい } }
関連1: クラス名のエイリアス
ファサードに関連する紛らわしい個所が2つある。1つはクラス名のエイリアスで、ファサードとは直接関係ないがほぼファサードに関連して使われている。
これはuse Request
がuse Illuminate\Support\Facades\Request
と同等になるあたりの話だ。
通常エイリアスを使うことはほぼ使わないと思うが、一部の素のPHPスクリプト上では、use
せず、エイリアスが直接使われていることがあるかもしれない。
php artisan ui vue --auth
などとして認証機能を有効にした場合、routes/web.php
にAuth::routes()
が追加されるが、このときファイル上部ではuse
Illuminate\Support\Facades\Auth
とはされていないはずだ。
これはエイリアスのおかげで動作している。
エイリアスは単純に、spl_autoload_register()
を使って、動的にclass_alias()
されることで設定されている。
設定されているエイリアスに関しては、Illuminate\Foundation\AliasLoader::getInstance()->getAliases()
をtinker等から実行すれば確認できる。
これらのほとんどはconfig/app.php
のaliases
で設定されるものだが、一部Composerでインストールされたパッケージから設定されるものがある。composer.lock
(じっさいにはinstalled.json
から読まれているが)のパッケージ設定にextra.laravel.aliasesがある場合、config/app.php
のaliases
のものに追加でエイリアスが作られる。
デフォルトではLaravel 7のエラー表示パッケージであるIgnitionから、Flareクラスのエイリアスが設定されているはずだ。
// ... { "name": "facade/ignition", // ... "extra": { "laravel": { "aliases": { "Flare": "Facade\\Ignition\\Facades\\Flare" } } }, }, // ...
関連2: Arr, Str
もう1つはIlluminate\Support
以下の、Arr, Strクラスだ。これらのクラスはファサードではないが、静的メソッドの使用がメインで、かつエイリアスも設定されているため、場合によってはファサードと区別がしづらいかもしれない。
とはいえ区別する必要もあまりない。クラスのソースコードを見れば、一目で裏にサービスインスタンスのない静的メソッドだけのクラスとわかる。
Let's Encryptの証明書の期限切れ通知メールをunsubscribeすると現状どうしようもないっぽい
ある日更新が不要になったLet's Encryptで取得した証明書の期限切れ通知メールを見てて、unsubscribeと書いてあるのに気付いてふとクリックしてしまった。
クリックした後の感じでは、どうもこの通知解除は、あるドメインに対してだけではなくメールアドレス単位で解除されるようだ。
それはちょっと不便なのでなんとかならないかと調べてみたが、どうしようもなさそう。
公式の情報。別のメールアドレス使ってねとある。
Expiration Emails - Let's Encrypt - Free SSL/TLS Certificates
Let's Encryptのコミュニティの情報。2016年から問題にされていて、2020年になっても十分な対策はないようだ。
Accidentally Unsubscribed - Let's Encrypt Community Support
オチはない。
Googleの検索結果一覧で狭い画面でも横スクロールバーが出ないようにする
いつの間にかmin-widthが1261pxとかになっていて、ブラウザのウィンドウは小さめ派には微妙な感じになっていたのでなんとかしてみた。
#appbar, .rhscol, #top_nav, div#searchform, #fbar { min-width: 0px; }
まずmin-widthが1261pxに設定されているすべての要素のmin-widthを0pxにする。これで表示領域1168pxまでは横スクロールバーが出ないようになる。
個人的にはこれで十分だったが、もう少し小さなウィンドウでブラウジングしてる人はさらに以下のスタイルシートを当てる。
#rhs { display: none; }
これが謎要素で、子を見た感じ表示する必要がそもそもなさそうなんだが表示されていて、しかも珍妙なサイズの左マージンが設定されているせいで表示に影響している。
とりあえず問題なさそうなので非表示にすることで、今度は表示領域852pxまでは横スクロールバーが表示されなくなる。
Gitであるauthorが変更したファイル一覧が欲しい
git diff --name-only
とかで行けるかなと思ったけど、git diff
はどうもauthorの指定はできないようだった。まあ仕組みを考えると妥当か。
ちょっとややこしいけど、以下のようにしてgit log
でそれっぽい感じで取れた。--name-only
じゃなくて--name-status
なのは、ファイル名とコミット概要部分を分けやすいようにしてるだけ。
git log --name-status --oneline --author='Author Name' | sort -u | grep -e '^\w\s'
Laravelのフォームリクエストのジレンマ
Laravelにはフォームリクエストという便利な機能がある。コントローラに適切なフォームリクエストをインジェクションさせると、リクエストがフォームリクエスト内のルールでバリデーションされた上でインジェクションされる。
<?php namespace App\Http\Requests; class UserRequest extends \Illuminate\Foundation\Http\FormRequest { public function rules() { return [ 'name' => ['string'], 'email' => ['email'], ]; } }
<?php namespace App\Http\Controllers; class UserController extends Controller { public function store(\App\Http\Requests\UserRequest $request) { dd($request->validated()); # バリデーション済みのデータが取得できる } }
上記のコードは、コントローラ側で以下のように実装しても同等だ。
<?php namespace App\Http\Controllers; class UserControllerWithValidation extends Controller { public function store(\Illuminate\Http\Request $request) { dd($request->validate([ 'name' => ['string'], 'email' => ['email'], ])); } }
フォームリクエストを使わない方がコードは短くなる。ではなぜフォームリクエストを使うのか? それはフォームリクエストを使うことでコントローラとバリデーションの双方向の依存を、コントローラからバリデーションだけの一方向の依存にできるからだ。
ではすべてのバリデーションをフォームリクエストで行おう、となりそうだが、じつはフォームリクエストは以下のようなルールで破綻する。
<?php namespace App\Http\Controllers; class UserControllerWithValidation extends Controller { public function update(\Illuminate\Http\Request $request, \App\User $user) { dd($request->validate([ 'name' => ['string'], 'email' => ['email', \Illumination\Validation\Rule::unique('users')->ignore($user->id)], ])); } }
バリデーションルールがルートと関連したモデルに依存してしまっている。このような、よくある、ちょっと複雑な例にフォームリクエストを使うことは意味がない。
フォームリクエストの利点はコントローラに依存しなくなることだが、ユーザモデルのインスタンスを取得するためにはコントローラに依存するか、もっと酷いことにルートに直接依存することになる。
フォームリクエストは Illuminate\Http\Requests
を拡張していて、同様に route
メソッド経由でルートにアクセスでき、そこからユーザモデルのインスタンスを取得できるが、コントローラ側でDIで間接的にルートから取得する方がまだマシだ。
ということで、フォームリクエストはコントローラへの依存を切り離せるので便利だが、依存を切り離すとコントローラに依存しないと用意できない複雑なルールに対応できなくなるので、じっさいにはほとんど使い所はない。
<?php namespace App\Http\Requests; class UserRequest extends \Illuminate\Foundation\Http\FormRequest { public function rules() { $user = $this->route()->parameter('user'); # 持って来ること自体はできるのだが return [ 'name' => ['string'], 'email' => ['email', \Illumination\Validation\Rule::unique('users')->ignore($user->id)], ]; } }
バリデーションルールは、素直にコントローラ内なりに書くのがよさそうだ。
LaravelでRefreshDatabaseしているテストをどう安全に運用するか
課題
RefreshDatabaseはテストごとにデータベースをクリアしてくれる便利なトレイトで、データベースを使ったテストを書くときにはほぼ必須だが、運用をミスると消したくないデータベースを消してしまうことがある。
具体的に危険なのは、.env.testing
がない場合に.env
が使われることにより、.env
い記載されたテスト用以外のデータベースにつないでしまい、クリアしてしまうこと。
Laravel 6.8.0以降ではphpunit.xml
でSQLiteのインメモリデータベースに接続するように設定されるようになっている(参考)ため、phpunit.xml
を変更していないのであればこの問題は避けられる。
(が、LaravelはRDBMS間の差異を十分に吸収しないため、テストにSQLiteを使うのは実用的ではなく、大抵の場合はこの部分の設定を消して、.env.testing
に書くことになるだろう)
解決策
本番環境では、まずそもそもPHPUnitが入っていないはずなのであまり問題ないはず。
問題は開発環境だ。.env
は用意せず、.env.local
を用意して、アプリケーション動作時にはAPP_ENV=local
だけ別途指定する形にすれば、間違って.env.testing
がない状態で自動テストを叩くようなことがあっても安全そうだ。が、まだ十分には試していない。
すべてのテストをデータベースの状態に依存させずに書ければそれがベストだと思うけど、なかなか難しいよね。
その他
RefreshDatabaseはmigrate:fresh
コマンドを実行することでデータベースをクリアしている。このコマンドはproduction
下では実行前に確認プロンプトを出してくれるのだが、そもそもテストがproduction
で実行されることは普通ない。