cakephp4で躓いた記録
cookie management
// 開発環境

chrome: 100

cakephp: 4.3

php: 8.0

docker
cookie の expires を過去の日時で指定する手段は3種類ある。上記のうち3つ目は2つ目をラップしたメソッドなので実質は2つとなる。このうち引数のないメソッドに関しては内部で expires に下記がセットされる。
$new->expiresAt = new DateTimeImmutable('1970-01-01 00:00:01');
cookie の仕様として set-cookie ヘッダの name, domain, path がブラウザが保持するクッキーと一致することで操作が可能となる。この際に expires に対して過去の日時がセットされた場合、ブラウザによって cookie が無効化される。しかし、上記の引数のないメソッドで無効化を図ろうとすると set-cookie ヘッダから expires がなくなり、ブラウザにあるクッキーがセッションクッキーとなるだけで終わってしまい、完全な無効化を計ることができない状態となる。 cakephp の思惑なのかミスなのかはわからないが確実にクッキーを削除するには引数ありのメソッドを使う必要がある。
察するに cakephp の config でタイムゾーンを Asia/Tokyo にしていると GMT への変換の際に9時間巻き戻されるため unix エポックより過去の日時となってしまい、無効な値となって expires をロストしていると思われる。
# テストコード

setcookie('test', 'test', ['expires' => -100]);

var_dump(headers_list());



#result

array(2) {

	[0]=> string(24) "X-Powered-By: PHP/8.0.18"

	[1]=> string(21) "Set-Cookie: test=test"

}
ResponseEmitter::setCookie($cookie): boolデータベースからデータを取り出すとき
不要なデータがいっぱいくっついて出てきたので除去する方法を漁ったのでパターンにわけて記しておく。
# 何も施してない状態

$row = $members->find()

		->where(['email' => $email)

		->limit(1)

		->toArray();

var_dump($row);

exit();



# result

array(1) {

	[0]=> object(App\Model\Entity\Member)#142 (13) {

		["id"]=> int(1)

		["email"]=> string(15) "xxx@example.com"

		["created"]=> object(Cake\I18n\FrozenTime)#151 (3) {

			["date"]=> string(26) "2022-09-14 23:55:27.000000"

			["timezone_type"]=> int(3)

			["timezone"]=> string(10) "Asia/Tokyo"

		}

		["status"]=> string(6) "enable"

		["[new]"]=> bool(false)

		["[accessible]"]=> array(3) {

			["email"]=> bool(true)

			["created"]=> bool(true)

			["status"]=> bool(true)

		}

		["[dirty]"]=> array(0) { }

		["[original]"]=> array(0) { }

		["[virtual]"]=> array(0) { }

		["[hasErrors]"]=> bool(false)

		["[errors]"]=> array(0) { }

		["[invalid]"]=> array(0) { }

		["[repository]"]=> string(7) "Members"

	}

}
# entity関連が消える

$row = $members->find()

		->disableHydration()

		->where(['email' => $email)

		->limit(1)

		->toArray();

var_dump($row);

exit();



# result

array(1) {

	[0]=> array(4) {

		["id"]=> int(1)

		["email"]=> string(15) "xxx@example.com"

		["created"]=> object(Cake\I18n\FrozenTime)#150 (3) {

			["date"]=> string(26) "2022-09-14 23:55:27.000000"

			["timezone_type"]=> int(3)

			["timezone"]=> string(10) "Asia/Tokyo"

		}

		["status"]=> string(6) "enable"

	}

}
# 日時情報をただの文字列として取得できる

$row = $members->find()

		->disableHydration()

		->disableResultsCasting()

		->where(['email' => $email)

		->limit(1)

		->toArray();

var_dump($row);

exit();



# result

array(1) {

	[0]=> array(4) {

		["id"]=> string(1) "1"

		["email"]=> string(15) "xxx@example.com"

		["created"]=> string(19) "2022-09-14 23:55:27"

		["status"]=> string(6) "enable"

	}

}
Cake\I18n\FrozenTimeではなくただの日時文字列を取るにはdisableResultsCastingメソッドを実行すればいい。
極力ORMは使いたくない
アソシエーションとかめんどくさいのでクエリを生で書きたいまであるけどちょっと譲歩してクエリビルダーだけ使ってやるかという考えなのでとりあえずクエリビルダーを取り出す手順を踏んでおく。
# Cake\Datasource\ModelAwareTrait

$this->loadModel('tablename');



# Cake\ORM\Locator\LocatorAwareTrait

$this->fetchTable('tablename');



# Cake\ORM\TableRegistry

TableRegistry::getTableLocator()->get('tablename')
いずれもCake\Controller\Controllerを継承した各コントローラ内からの使い方である。上2つはコントローラにtraitでセットされているが、useを追加してstaticメソッドにアクセスすることになるので書くのがいちいちめんどくさい。戻り値は全部同じである。
次にクエリビルダーの取り出しについては下記の2つの手段がある。
# select

$tableObject->find();



# insert, update, delte

tableObject->query();
公式ドキュメントでinsert文のときにfindを使うなと書いてある理由として、find()の第二引数であるoptionsを戻り値であるCake\ORM\Queryに渡しており、作用が変わる可能性があるからである。