PHPの小ネタメモ
PHP系の調べて理解した小ネタを投稿していく
過去の分を何も残していないのがもったいない
スクラップなので、情報は信じすぎないでほしい
laravelのvalidationでemail:strictを指定したときに落ちるパターン(括弧が入ってると落ちる)
バリデーションのルールを深追いするのもめんどいので、email:filter,strict,dnsを指定して妥協
(filterは日本語を含んでいる場合を弾く、dnsはそのメールのドメイン的が存在するか的なで@example.comとかだと落ちる)
Contextual bindingはServiceProviderで行うが、method injectionには対応しないし、開発者が対応させる気がない。
まあ、そもそもmethod injectionのbindをServiceProviderで行うのはアーキテクチャ的には綺麗だが、運用上bindを遠いところでするのは避けた方が良いという考えもある。
(おそらく唯一の)代案としてはconstructorで通常通りmethod injectionを変数込みでやる感じで、そのクラスのメソッドで使うものをここでbindさせるもの、意味を考えたらこれがむしろ適切まである。
phpでarrayをforeach文を回すとき、直接値を書き換えたい時は参照渡しで行う(マニュアル参照)かkeyを元にarrayにアクセスして書き換えることが可能。
keyを書き換えたいときは書き換え先のkeyの要素を作成した後に書き換え元のkeyをunsetで削除する。
基本的にforeachの中で回してる配列の処理を行うのは悪手なので注意が必要。array_系の関数で対応した方が良い。
ちなみに、PHPではおそらくforeachで回したいキーのイテレータを先に作っておいている感じなので、keyを新しく追加してもその追加したキーについてはforeach内の処理は適用されない(無限ループの可能性を回避できる)。
PHP8.0から8.1へのアップグレード面白ポイント
- enum -> 熱い
- 継承されたメソッド内でstatic変数を使うと親クラスと共有されるように仕様変更
- staticプロパティの場合は共有してるので、挙動が同じになった感じ
- 必須の引数の前にデフォルト値を持つ引数を指定すると、デフォルト値を持つ引数は必須の引数として扱われる
-
function a($b=“1”, $c)
でa(2)とすると$cが未定義になる
-
- 暗黙のfloat->intの変換を非推奨
- トレイトのstaticなメソッドに直接アクセスするのを非推奨
PHP8.1から8.2へのアップグレード面白ポイント
- トレイトの中でconst定義可能に
- str_splitが空文字を渡したときに空配列を返すように
- 今までは空文字列の要素を一つ返していた
- call_user_funcで呼び出せないcallableが非推奨
実装上はvoidはnullとして実装されているため、return;とした時の返り値は実装上null。つまり、voidを型宣言したとしてもnullでの実装なので戻り値はnullで受け取れてしまう。ただ、型でvoidとした時に明示的にreturn nullとするとTypeError。
PHPの変数はコピーオンライトなので、参照渡しの場合はむしろ低速化したりメモリを浪費したりするパターンがある。
参照渡しについて理解して使うべきだし、まずは参照渡しを避けるような書き方を意識する。
最近の言語はこの辺りの高速化を隠蔽しているので検索して見つけるのすら難しいが、この辺りにも目を配れるようにすると良い。
PHPは配列が全部連想配列。しかも、配列の足し算は破壊的みたいな挙動をするので、[‘a’]+[‘b’]は[‘a’, ‘b’]を返すのではなく、[‘a’]になる。array_mergeを使えばちゃんと[‘a’, ‘b’]を返す。
Laravelの探したいソースコードを見ようとしてもインターフェース(Contracts)で抽象化されていて見つけられないことが多い、インターフェースまで発見できているのに…という時は、vendor/laravel/framework/src/illuminate/Contracts/YYY/XXX.phpを見ているので、vendor/laravel/framework/src/illuminate/YYY/XXX.phpに具象を発見できる
phpにはcheck_dateという関数でグレゴリオ暦における妥当性をチェックする機能がある
Carbonは日時の扱いでよく使われるが、下記Issueでcheck_date的なのをしてからCabonのインスタンスを作る静的メソッドが作られている。現時点での最新コミットの実装を見たら、下記の感じになっていた。strict modeをfalseにすると、falseが返るようになる。trueの場合はエラーをthrowする。
selfは定義時、staticは実行時のクラスを指すので注意が必要
(直感的には逆だけど…)
UnitのsetUpメソッドはdataproviderで与えた全パターンで毎回再実行されるが、DBを毎回のパターンでいじらない場合は高速化のためにsetUp内で再シーディングしたくない。
setUpでフラグ管理して行う方法を自分で書いて他の記事等でも書いていたが、付属のメソッドがあるか調べ、setUpBeforeClassを使えることに気づいた。
setUpBeforeClassを使うと、そのクラスで初めにテストケースを通過する場合のみの実行を行うことができるが、ファサードを呼ぶことができない。
ただ、Artisanのファサードを使うのは楽なので、再シーディングについてはフラグ管理で妥協する。おそらく、依存解決ができるようにすれえばファサードは呼べるが、下記PRにもあるが非推奨っぽいのでやめておく。
laravel 9.xからSoftDeleteの仕様が一部変更になっているので、使う人は注意
一応、9.xに対応する自分の実装を貼っておく
delete_flagという命名は元のDBのカラム名がこうなってしまっているため…、自分ではどうにもできません
実装
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
trait SoftDeletesBoolean
{
use SoftDeletes;
use SoftDeletesBooleanDeleteFlag;
protected static $DELETED_AT = 'delete_flag';
/**
* Boot the soft deleting trait for a model.
*
* @return void
*/
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingBooleanScope);
}
/**
* Initialize the soft deleting trait for an instance.
*
* @return void
*/
public function initializeSoftDeletes()
{
if (! isset($this->casts[$this->getDeletedAtColumn()])) {
$this->casts[$this->getDeletedAtColumn()] = 'integer';
}
}
/**
* Perform the actual delete query on this model instance.
*
* @return void
*/
protected function runSoftDelete()
{
$query = $this->setKeysForSaveQuery($this->newModelQuery());
$time = $this->freshTimestamp();
$columns = [$this->getDeletedAtColumn() => static::$DELETED];
$this->{$this->getDeletedAtColumn()} = static::$DELETED;
if ($this->usesTimestamps() && ! is_null($this->getUpdatedAtColumn())) {
$this->{$this->getUpdatedAtColumn()} = $time;
$columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time);
}
$query->update($columns);
$this->syncOriginalAttributes(array_keys($columns));
$this->fireModelEvent('trashed', false);
}
/**
* Restore a soft-deleted model instance.
*
* @return bool|null
*/
public function restore()
{
// If the restoring event does not return false, we will proceed with this
// restore operation. Otherwise, we bail out so the developer will stop
// the restore totally. We will clear the deleted timestamp and save.
if ($this->fireModelEvent('restoring') === false) {
return false;
}
$this->{$this->getDeletedAtColumn()} = static::$DELETE_FLAG_NO;
// Once we have saved the model, we will fire the "restored" event so this
// developer will do anything they need to after a restore operation is
// totally finished. Then we will return the result of the save call.
$this->exists = true;
$result = $this->save();
$this->fireModelEvent('restored', false);
return $result;
}
/**
* Determine if the model instance has been soft-deleted.
*
* @return bool
*/
public function trashed()
{
return $this->{$this->getDeletedAtColumn()} === static::$DELETED;
}
/**
* Get the name of the "deleted at" column.
*
* @return string
*/
public function getDeletedAtColumn()
{
return static::$DELETED_AT;
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SoftDeletingBooleanScope extends SoftDeletingScope
{
use SoftDeletesBooleanDeleteFlag;
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where($model->getQualifiedDeletedAtColumn(), self::$NOT_DELETED);
}
/**
* Extend the query builder with the needed functions.
*
* @return void
*/
public function extend(Builder $builder)
{
foreach ($this->extensions as $extension) {
$this->{"add{$extension}"}($builder);
}
$builder->onDelete(function (Builder $builder) {
$column = $this->getDeletedAtColumn($builder);
return $builder->update([
$column => self::$DELETED,
]);
});
}
/**
* Add the restore extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addRestore(Builder $builder)
{
$builder->macro('restore', function (Builder $builder) {
$builder->withTrashed();
return $builder->update([$builder->getModel()->getDeletedAtColumn() => self::$NOT_DELETED]);
});
}
/**
* Add the without-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addWithoutTrashed(Builder $builder)
{
$builder->macro('withoutTrashed', function (Builder $builder) {
$model = $builder->getModel();
$builder->withoutGlobalScope($this)->where($model->getQualifiedDeletedAtColumn(), self::$NOT_DELETED);
return $builder;
});
}
/**
* Add the only-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addOnlyTrashed(Builder $builder)
{
$builder->macro('onlyTrashed', function (Builder $builder) {
$model = $builder->getModel();
$builder->withoutGlobalScope($this)->where($model->getQualifiedDeletedAtColumn(), self::$DELETED);
return $builder;
});
}
}
<?php
namespace App\Models;
trait SoftDeletesBooleanDeleteFlag
{
protected static $NOT_DELETED = 0;
protected static $DELETED = 1;
}
そういえば、Laravelに以前コントリビュートした。
ソースコードを追う癖をつけておくと、特にDocの部分にはミスが結構あることに気づく。
laravelのソースコードでよく見かけるtapという関数
これは、laravelのヘルパであり、tapの第一引数に与えた引数を第二引数に渡したクロージャーの引数にわたし、そのクロージャーでの処理を行なってから第一引数を返すようにできる
何か作用させたい時に使える
ちょっと古いけど、Laravelのデザインパターン
Laravel10.xで引数戻り値への型定義が入る
Laravel * ReactベースでのSPAにおけるセキュリティ入門
nullsafe便利、無駄な入れ子が減る
$result = $repository?->getUser(5)?->name;
LaravelのHTTPクライアントとしてguzzleが有名
Eloquentでアクティブレコードパターンになっている以上、ドメインモデルをエンティティと切り離せないんだよな
結局、下記記事のような妥協案に落ち着く
doctrineの選択肢
次に何か作るときはeloquentじゃなくてdoctrineを採用したい
CORS周りの理解が怪しく特に参考になった記事
これええな
Class宣言あたりのエラーはoptimiseし直す
composer dump-autoload -o
mockの場合は、あるメソッドのみをモックするものだと思っていたら違った。
特定のメソッドのみの場合はmakePartial
PHPUnitにMockeryを統合するときは、こんな感じ
スタンドアローンであること忘れずに