<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PHP | きいちログ</title>
	<atom:link href="https://wptech.kiichiro.work/tag/php/feed/" rel="self" type="application/rss+xml" />
	<link>https://wptech.kiichiro.work</link>
	<description>WordPressとかAWSとかPHPとか</description>
	<lastBuildDate>Wed, 16 Jul 2025 02:16:58 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>Laravel Starter Kits × WorkOSでEntra IDとSSOしたときの記録</title>
		<link>https://wptech.kiichiro.work/21jheu6bf8/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Tue, 15 Jul 2025 09:36:32 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Entra ID]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3849</guid>

					<description><![CDATA[目次 はじめに準備LaravelアプリケーションEntra ID側の準備とSAML接続ハマったポイントアプリケーションへのアクセス権がなくて拒否される (AADSTS50105)ドメインの設定漏れでSign in画面がル [&#8230;]]]></description>
										<content:encoded><![CDATA[

  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-2" checked><label class="toc-title" for="toc-checkbox-2">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">準備</a><ol><li><a href="#toc3" tabindex="0">Laravelアプリケーション</a></li><li><a href="#toc4" tabindex="0">Entra ID側の準備とSAML接続</a></li></ol></li><li><a href="#toc5" tabindex="0">ハマったポイント</a><ol><li><a href="#toc6" tabindex="0">アプリケーションへのアクセス権がなくて拒否される (AADSTS50105)</a></li><li><a href="#toc7" tabindex="0">ドメインの設定漏れでSign in画面がループする</a></li><li><a href="#toc8" tabindex="0">属性のマッピング不正でログインに失敗する</a></li></ol></li><li><a href="#toc9" tabindex="0">さいごに</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">はじめに</span></h2>



<p>先日、Laravel 12 から Breeze と Jetstream の代わりに Starter Kits がサポートされたということを知りました。Starter Kits では WorkOS が公式にサポートされ、WorkOS 経由の SSO が比較的簡単に導入できるようになっていました。</p>




<a rel="noopener" href="https://laravel.com/starter-kits" title="Starter Kits - Laravel - The PHP Framework For Web Artisans" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://laravel.com/images/og/laravel-starter-kits.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Starter Kits - Laravel - The PHP Framework For Web Artisans</div><div class="blogcard-snippet external-blogcard-snippet">Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravel.com/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravel.com</div></div></div></div></a>




<a rel="noopener" href="https://laravel.com/docs/12.x/starter-kits" title="Starter Kits - Laravel 12.x - The PHP Framework For Web Artisans" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://laravel.com/images/og/laravel-docs-12.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Starter Kits - Laravel 12.x - The PHP Framework For Web Artisans</div><div class="blogcard-snippet external-blogcard-snippet">Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravel.com/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravel.com</div></div></div></div></a>



<p>せっかくなので Laravel Breeze で作ったアプリを Starter Kits を利用して作り直し、認証を WorkOS に寄せて Entra ID で SSO ログインをしてみました。実際にやってみると、WorkOS 側で拒否されるドメインがあったり、Entra ID 側の設定でログインに失敗するといったことがあり、結構なハマりポイントが多かったです。</p>



<p>今回は、導入までにハマったポイントを中心に記録を残していきます。</p>



<div class="wp-block-cocoon-blocks-tab-box-1 blank-box bb-tab bb-check block-box">
<p>WorkOS は、Google や Microsoft などのサービスを使ったソーシャルログインや SAML 認証で SSO を実現できる認証プロバイダーです。WorkOS を利用するには WorkOS アカウントが必要です。</p>




<a rel="noopener" href="https://workos.com" title="WorkOS — Your app, Enterprise Ready." class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://cdn.prod.website-files.com/621f54116cab10f6e9215d8b/627321b887917b110d342e2b_homepage.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">WorkOS — Your app, Enterprise Ready.</div><div class="blogcard-snippet external-blogcard-snippet">Developer APIs/SDKs for Enterprise Ready features like Single Sign-On, Directory Sync, Audit Logging, and more. Get star...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://workos.com" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">workos.com</div></div></div></div></a>
</div>



<h2 class="wp-block-heading"><span id="toc2">準備</span></h2>



<h3 class="wp-block-heading"><span id="toc3">Laravelアプリケーション</span></h3>



<p><code>laravel</code> コマンドをあらかじめインストールしておきます。<code>laravel new</code> で新しい Laravel プロジェクトを対話形式で作成できます。このとき、認証プロバイダーを選択できるので WorkOS を選んでおきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code> ┌ Which authentication provider do you prefer? ────────────────┐
 │   ○ Laravel&#39;s built-in authentication                        │
 │ › ● WorkOS (Requires WorkOS account)                         │
 └──────────────────────────────────────────────────────────────┘</code></pre></div>



<p>WorkOS の API を利用するために <code>.env</code> も編集しておきます。各値は WorkOS のダッシュボードから確認できます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>WORKOS_CLIENT_ID=xxxx
WORKOS_API_KEY=xxxx
WORKOS_REDIRECT_URL=&quot;http://localhost:8000/authenticate&quot;</code></pre></div>



<h3 class="wp-block-heading"><span id="toc4">Entra ID側の準備とSAML接続</span></h3>



<p>Entra ID 側で WorkOS と SAML 接続できるように設定をしておきます。手順はドキュメントの通りで大丈夫です。</p>




<a rel="noopener" href="https://workos.com/docs/integrations/entra-id-saml" title="Entra ID SAML (formerly Azure AD) – Integrations – WorkOS Docs" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://og-images.workos.com/api/docs/?t=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjYXRlZ29yeSI6IkludGVncmF0aW9ucyIsInRpdGxlIjoiRW50cmEgSUQgU0FNTCAoZm9ybWVybHkgQXp1cmUgQUQpIiwiZGVzY3JpcHRpb24iOiJMZWFybiBob3cgdG8gY29uZmlndXJlIGEgY29ubmVjdGlvbiBFbnRyYSBJRCB2aWEgU0FNTCIsImlhdCI6MTc1MjA3ODY0OH0.GBl9X3yNSCpCh78HRqAaMD3klfQb9VZJDgRfNaKrDvA" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Entra ID SAML (formerly Azure AD) – Integrations – WorkOS Docs</div><div class="blogcard-snippet external-blogcard-snippet">Learn how to configure a connection Entra ID via SAML.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://workos.com/docs/integrations/entra-id-saml" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">workos.com</div></div></div></div></a>



<p>ACS URL と SP Entity の場所が若干わかりづらいですが、Organizations から対応するものを選択して View connections ボタンを押下した先にあります (執筆当時)。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="166" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/3cdf05bb6012e22c0373e0b2b343b056-1024x166.png" alt="Azure SSO の organization の設定画面。Authentication のエリアに Single Sign-On ブロックがあり、View connection ボタンがある。" class="wp-image-3894" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/3cdf05bb6012e22c0373e0b2b343b056-1024x166.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/3cdf05bb6012e22c0373e0b2b343b056-300x49.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/3cdf05bb6012e22c0373e0b2b343b056-768x125.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/3cdf05bb6012e22c0373e0b2b343b056-1536x249.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/3cdf05bb6012e22c0373e0b2b343b056-2048x332.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="200" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/a0a9f54e9a52c6a8f8247663ed908919-1024x200.png" alt="Service Provider Details のブロックに SP Entity ID, ACS URL, SP Metadata が表示されている。" class="wp-image-3897" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/a0a9f54e9a52c6a8f8247663ed908919-1024x200.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/a0a9f54e9a52c6a8f8247663ed908919-300x59.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/a0a9f54e9a52c6a8f8247663ed908919-768x150.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/a0a9f54e9a52c6a8f8247663ed908919-1536x301.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/a0a9f54e9a52c6a8f8247663ed908919-2048x401.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>UI は度々変更になるようですので、見当たらなければ根気よく探す必要があります。</p>



<h2 class="wp-block-heading"><span id="toc5">ハマったポイント</span></h2>



<h3 class="wp-block-heading"><span id="toc6">アプリケーションへのアクセス権がなくて拒否される (AADSTS50105)</span></h3>



<p>アプリのログイン画面 (または AuthKit) でメールアドレスを入力して送信すると、以下のようなメッセージが表示されます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>AADSTS50105: Your administrator has configured the application HOGE SAML App (&#39;xxxxx&#39;) to block users unless they are specifically granted (&#39;assigned&#39;) access to the application. The signed in user &#39;foo@bar.example.com&#39; is blocked because they are not a direct member of a group with access, nor had access directly assigned by an administrator. Please contact your administrator to assign access to this application.</code></pre></div>



<p>これは、ログインしようとしたユーザーに当該アプリケーションへのアクセスが許可されていないことに起因します。</p>




<a rel="noopener" href="https://learn.microsoft.com/ja-jp/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50105-user-not-assigned-role" title="エラー AADSTS50105 - サインインしているユーザーがアプリケーションのロールに割り当てされていません。" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://learn.microsoft.com/en-us/media/open-graph-image.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">エラー AADSTS50105 - サインインしているユーザーがアプリケーションのロールに割り当てされていません。</div><div class="blogcard-snippet external-blogcard-snippet">Microsoft Entra SSO を使用して SAML ベースの構成済みアプリにサインインするときにAADSTS50105 エラーが発生する問題について説明します。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://learn.microsoft.com/ja-jp/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50105-user-not-assigned-role" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">learn.microsoft.com</div></div></div></div></a>



<p>Entra 管理センターまたは Azure ポータルからユーザーを追加することで解消ができるはずです。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="437" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/93666232d5217bfed963101834ddff81-1024x437.png" alt="" class="wp-image-3910" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/93666232d5217bfed963101834ddff81-1024x437.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/93666232d5217bfed963101834ddff81-300x128.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/93666232d5217bfed963101834ddff81-768x328.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/93666232d5217bfed963101834ddff81-1536x656.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/93666232d5217bfed963101834ddff81-2048x875.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading"><span id="toc7">ドメインの設定漏れでSign in画面がループする</span></h3>



<p>WorkOS で SSO を利用するには、事前にドメインの検証と設定が必要です。この設定が漏れていると Sign in 画面から先に進めずループしてしまう挙動を示すことがあります。</p>



<p>ドメインの検証方法には、次の2つがあります。</p>



<ul class="wp-block-list">
<li>Self-serve Domain Verification</li>



<li>Manual Domain Verification</li>
</ul>



<p>詳しくは公式ドキュメントをご覧ください。</p>




<a rel="noopener" href="https://workos.com/docs/user-management/domain-verification/manual-domain-verification" title="Domain Verification – AuthKit – WorkOS Docs" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://og-images.workos.com/api/docs/?t=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjYXRlZ29yeSI6IkF1dGhLaXQiLCJ0aXRsZSI6IkRvbWFpbiBWZXJpZmljYXRpb24iLCJkZXNjcmlwdGlvbiI6IlZlcmlmeSBvcmdhbml6YXRpb24gZG9tYWlucyBmb3Igc2VjdXJlIGF1dGhlbnRpY2F0aW9uIGFuZCBwcm92aXNpb25pbmciLCJpYXQiOjE3NTI0NDI3NTl9.ALGVuXT5rGp7Z0W_mHsesVdWSkJNxC3e16J8oDYUpOA" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Domain Verification – AuthKit – WorkOS Docs</div><div class="blogcard-snippet external-blogcard-snippet">Verify organization domains for secure authentication and provisioning.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://workos.com/docs/user-management/domain-verification/manual-domain-verification" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">workos.com</div></div></div></div></a>



<p>Manual Domain Verification を利用する場合、既に Entra ID などで所有が確認されたカスタムドメインを、WorkOS の設定に手動で追加します。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="314" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/b3684cfc898ab16bc696b6244a8d36aa-1024x314.png" alt="" class="wp-image-3923" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/b3684cfc898ab16bc696b6244a8d36aa-1024x314.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/b3684cfc898ab16bc696b6244a8d36aa-300x92.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/b3684cfc898ab16bc696b6244a8d36aa-768x236.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/b3684cfc898ab16bc696b6244a8d36aa-1536x471.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/b3684cfc898ab16bc696b6244a8d36aa-2048x628.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>ここでは所有が確認できるドメインを入力するので、<code>gmail.com</code> や <code>outlook.com</code> などの <code>public consumer domain</code> を入力しようとすると拒否されます。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="492" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/e12c23da18e887cb64deb7cbe61829e5-1024x492.png" alt="" class="wp-image-3932" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/e12c23da18e887cb64deb7cbe61829e5-1024x492.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/e12c23da18e887cb64deb7cbe61829e5-300x144.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/e12c23da18e887cb64deb7cbe61829e5-768x369.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/e12c23da18e887cb64deb7cbe61829e5.png 1224w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading"><span id="toc8">属性のマッピング不正でログインに失敗する</span></h3>



<p>ログインに失敗すると、Notifications に以下のログが残る場合があります。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="398" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/06f1c8289ab1f8b22d7cf6aebeaf5eda-1024x398.png" alt="" class="wp-image-3940" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/06f1c8289ab1f8b22d7cf6aebeaf5eda-1024x398.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/06f1c8289ab1f8b22d7cf6aebeaf5eda-300x117.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/06f1c8289ab1f8b22d7cf6aebeaf5eda-768x299.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/06f1c8289ab1f8b22d7cf6aebeaf5eda-1536x597.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/06f1c8289ab1f8b22d7cf6aebeaf5eda.png 1630w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>同様に Admin Email 宛に以下のようなメッセージも届いているかと思います。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>Single Sign-On failed for XXXXXX

Your foo.example.com connection received invalid user attributes and failed to authenticate a user.
 
Why this might happen
 
The user attributes are misconfigured in Entra ID (Azure AD).
A single user is misconfigured in Entra ID (Azure AD). (Check the received attributes below).
 
How to fix this
 
Verify that attribute mapping is correct in the Entra ID (Azure AD) dashboard.</code></pre></div>



<p>このエラーは、idP (今回の場合 Entra ID) から返却される attribute が WorkOS 側で期待しているものと異なっていることを示しています。</p>




<a rel="noopener" href="https://workos.com/docs/reference/sso/profile" title="API Reference – WorkOS Docs" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://og-images.workos.com/api/docs/?t=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjYXRlZ29yeSI6IkRvY3MiLCJ0aXRsZSI6IkFQSSBSZWZlcmVuY2UiLCJkZXNjcmlwdGlvbiI6IkNvZGUgc25pcHBldHMgYW5kIHR5cGUgZGVmaW5pdGlvbnMgZm9yIHRoZSBXb3JrT1MgY2xpZW50IGxpYnJhcmllcyIsImlhdCI6MTc1MjUzNTk5OX0.BQz8GOrNy5GXy9HrECck8Z-Jcu-BLJz7fLWSr2VZ5cY" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">API Reference – WorkOS Docs</div><div class="blogcard-snippet external-blogcard-snippet">Code snippets and type definitions for the WorkOS client libraries.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://workos.com/docs/reference/sso/profile" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">workos.com</div></div></div></div></a>



<p>WorkOS 側で期待している属性値と Entra ID 側から送っている属性がマッチしていることを確認します。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="409" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/d2a46a061d48ce1ccc2a38c2b6de1a83-1024x409.png" alt="" class="wp-image-3949" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/d2a46a061d48ce1ccc2a38c2b6de1a83-1024x409.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/d2a46a061d48ce1ccc2a38c2b6de1a83-300x120.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/d2a46a061d48ce1ccc2a38c2b6de1a83-768x307.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/d2a46a061d48ce1ccc2a38c2b6de1a83-1536x614.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/d2a46a061d48ce1ccc2a38c2b6de1a83-2048x819.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">WorkOS Attribute mapping</figcaption></figure>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="663" src="https://wptech.kiichiro.work/wp-content/uploads/2025/07/9e957d994777d28c8e8bf4c8ce9665a3-1024x663.png" alt="" class="wp-image-3952" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/07/9e957d994777d28c8e8bf4c8ce9665a3-1024x663.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/9e957d994777d28c8e8bf4c8ce9665a3-300x194.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/9e957d994777d28c8e8bf4c8ce9665a3-768x497.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/9e957d994777d28c8e8bf4c8ce9665a3-1536x994.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/07/9e957d994777d28c8e8bf4c8ce9665a3.png 1598w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Entra 管理センター 属性とクレーム</figcaption></figure>



<p>WorkOS 側で required となっている項目については、Entra ID 側で設定されていないとキー毎何も返さなくなるので、設定しておく必要があります。特に Starter Kits を利用する場合は姓名が必須になっているので注意が必要です。</p>



<p>また、姓名を漢字などのマルチバイト文字で登録すると何故か返って来ないという現象がありました (これについては未解決です)。</p>



<h2 class="wp-block-heading"><span id="toc9">さいごに</span></h2>



<p>本記事では、Laravel Starter Kit で WorkOS を利用し、Microsoft Entra ID と SAML SSO を組み合わせた際に直面した問題とその対処法をまとめました。これが実際のプロジェクトで導入しようとしている方々の助けになれば幸いです。</p>



<p>今回紹介したものは、WorkOS 特有のハマりポイントもありましたが、Entra ID や SAML の仕様で躓いた部分もありました。私自身が SAML への理解が浅いということが露呈した結果ではあります。精進します。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>AWS CDK と Bref を用いた Laravel サーバーレス構成 ― Lambda + EFS + SQLite</title>
		<link>https://wptech.kiichiro.work/94slab74qm/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Wed, 30 Apr 2025 06:26:38 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[TypeScript]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3700</guid>

					<description><![CDATA[目次 はじめにプロジェクトの概要コンセプト成果物システム構成準備LaravelCDKCDKの実装ネットワークデータストレージアプリケーションWebアプリケーションConsoleアプリケーションデプロイ・動作確認まとめと感 [&#8230;]]]></description>
										<content:encoded><![CDATA[

  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-4" checked><label class="toc-title" for="toc-checkbox-4">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">プロジェクトの概要</a><ol><li><a href="#toc3" tabindex="0">コンセプト</a></li><li><a href="#toc4" tabindex="0">成果物</a></li><li><a href="#toc5" tabindex="0">システム構成</a></li></ol></li><li><a href="#toc6" tabindex="0">準備</a><ol><li><a href="#toc7" tabindex="0">Laravel</a></li><li><a href="#toc8" tabindex="0">CDK</a></li></ol></li><li><a href="#toc9" tabindex="0">CDKの実装</a><ol><li><a href="#toc10" tabindex="0">ネットワーク</a></li><li><a href="#toc11" tabindex="0">データストレージ</a></li><li><a href="#toc12" tabindex="0">アプリケーション</a><ol><li><a href="#toc13" tabindex="0">Webアプリケーション</a></li><li><a href="#toc14" tabindex="0">Consoleアプリケーション</a></li></ol></li></ol></li><li><a href="#toc15" tabindex="0">デプロイ・動作確認</a></li><li><a href="#toc16" tabindex="0">まとめと感想</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">はじめに</span></h2>



<p>PHPでAWS Lambdaを利用したアプリケーションを構築するには、カスタムランタイムが必要になります。BrefはLambdaでPHPを実行するためのランタイムとLaravel、SymfonyなどのPHPフレームワークとの統合を提供しています。</p>




<a rel="noopener" href="https://bref.sh" title="Bref – Simple and scalable PHP with serverless" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://bref.sh/social-card.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Bref – Simple and scalable PHP with serverless</div><div class="blogcard-snippet external-blogcard-snippet">Bref is a framework to write and deploy serverless PHP applications on AWS Lambda.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://bref.sh" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">bref.sh</div></div></div></div></a>



<p>本記事では、このBrefを使ったLaravelアプリケーションの環境構築方法を解説していきます。</p>



<h2 class="wp-block-heading"><span id="toc2">プロジェクトの概要</span></h2>



<h3 class="wp-block-heading"><span id="toc3">コンセプト</span></h3>



<p>基本的にBrefはServerless Frameworkを介して利用しますが、今回はAWS CDKにBref用の専用Constructが用意されているので、CDKを利用することにしました。コードベースでのインフラ管理や拡張の容易性もCDKを選定した理由になります。</p>



<p>また、個人のテスト環境として比較的低コストで構築できるように最小構成で実装していきます。</p>



<h3 class="wp-block-heading"><span id="toc4">成果物</span></h3>



<p>完成品は以下に配置してあります。</p>




<a rel="noopener" href="https://github.com/ShotaroMuraoka/cdk-serverless-laravel" title="GitHub - ShotaroMuraoka/cdk-serverless-laravel: Laravel アプリケーションを Bref でサーバーレス化した CDK" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://opengraph.githubassets.com/9e5f1f036341ca90ff4e35999daa9468a3cfc0b7532da531ce456db400428a22/ShotaroMuraoka/cdk-serverless-laravel" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">GitHub - ShotaroMuraoka/cdk-serverless-laravel: Laravel アプリケーションを Bref でサーバーレス化した CDK</div><div class="blogcard-snippet external-blogcard-snippet">Laravel アプリケーションを Bref でサーバーレス化した CDK. Contribute to ShotaroMuraoka/cdk-serverless-laravel development by creating an ac...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://github.com/ShotaroMuraoka/cdk-serverless-laravel" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">github.com</div></div></div></div></a>



<h3 class="wp-block-heading"><span id="toc5">システム構成</span></h3>



<p>システム構成は以下の通りです。</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="901" height="461" src="https://wptech.kiichiro.work/wp-content/uploads/2025/04/bref-cdk.png" alt="AWSの構成図。クライアントからはHTTPとaws lambda invokeの線が出ており、それぞれAPI Gateway, Lambda ConsoleFunctionと接続している。API GatewayはApiFunctionと接続している。ApiFunctionとConsoleFunctionはEFSに接続している。API GatewayはAWS Cloudの中、Lambda ApiFunctionとConsoleFunction, EFSはAWS Cloudの中のVPCの中のPrivate subnetの中に配置されている。" class="wp-image-3719" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/04/bref-cdk.png 901w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/bref-cdk-300x153.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/bref-cdk-768x393.png 768w" sizes="(max-width: 901px) 100vw, 901px" /></figure>



<p>Lambda関数が2つデプロイされているのは、API Gatewayに統合されているWebアプリケーションと <code>php artisan migrate</code> などを実行するConsoleアプリケーションを分けているためです。EFSは後述しますが、データストレージの役割を担っています。</p>



<h2 class="wp-block-heading"><span id="toc6">準備</span></h2>



<h3 class="wp-block-heading"><span id="toc7">Laravel</span></h3>



<p>Laravel側の作業はBrefに必要なライブラリをインストールするだけです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>% composer require bref/bref bref/laravel-bridge --update-with-dependencies</code></pre></div>



<h3 class="wp-block-heading"><span id="toc8">CDK</span></h3>



<p>CDK側では、BrefのConstructパッケージをインストールします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>% npm install @bref.sh/constructs</code></pre></div>



<h2 class="wp-block-heading"><span id="toc9">CDKの実装</span></h2>



<p>CDK側の実装を見ていきます。</p>



<h3 class="wp-block-heading"><span id="toc10">ネットワーク</span></h3>



<p>EFSをマウントさせる都合上、VPC Lambdaである必要があります。コスト高になりがちなNAT Gatewayに関しては、今回はLambdaからインターネットに出ていく必要は無いので、作成をしない設定にしています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-ts" data-file="lib/construct/networking.ts" data-lang="TypeScript"><code>const vpc = new ec2.Vpc(this, &quot;BrefVpc&quot;, {
  maxAzs: 1,
  natGateways: 0,
  restrictDefaultSecurityGroup: true,
  subnetConfiguration: [
    {
      name: &quot;BrefPrivate&quot;,
      subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
  ],
});</code></pre></div>



<h3 class="wp-block-heading"><span id="toc11">データストレージ</span></h3>



<p>RDBのマネージドサービスは高価ですので、Laravelの初期状態で使えるSQLiteを選定し、EFSに <code>database.sqlite</code> ファイルを配置するようにします。冗長化も不要なのでOneZoneを選択しています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-ts" data-file="lib/construct/datastore.ts" data-lang="TypeScript"><code>const fileSystem = new efs.FileSystem(this, &quot;BrefEfs&quot;, {
  vpc: vpc,
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
  throughputMode: efs.ThroughputMode.BURSTING,
  oneZone: true,
});

const accessPoint = this.fileSystem.addAccessPoint(&quot;BrefAccessPoint&quot;, {
  path: &quot;/bref&quot;
  createAcl: {
    ownerUid: &quot;1001&quot;,
    ownerGid: &quot;1001&quot;,
    permissions: &quot;750&quot;,
  },
  posixUser: {
    uid: &quot;1001&quot;,
    gid: &quot;1001&quot;,
  },
});</code></pre></div>



<h3 class="wp-block-heading"><span id="toc12">アプリケーション</span></h3>



<p>システム構成の項で紹介した通り、WebアプリケーションとConsoleアプリケーションの2つを用意しています。</p>



<h4 class="wp-block-heading"><span id="toc13">Webアプリケーション</span></h4>



<p>Webアプリケーションでは、Lambda関数とHTTPからのアクセス用にAPI Gatewayを統合します。Lambda関数は <code>@bref.sh/constructs</code> に <code>PhpFpmFunction</code> というBrefのレイヤーを追加したConstructがあるので、これを使用します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-ts" data-file="lib/construct/backend.ts" data-lang="TypeScript"><code>const backendFn = new PhpFpmFunction(this, &quot;BrefApiFunction&quot;, {
  functionName: &quot;ApiFunction&quot;,
  handler: &quot;public/index.php&quot;,
  code: packagePhpCode(&quot;../laravel/&quot;, {
    exclude: [&quot;tests/**&quot;, &quot;var/**&quot;],
  }),
  timeout: cdk.Duration.seconds(28),
  memorySize: 1024,
  vpc: vpc,
  filesystem: lambda.FileSystem.fromEfsAccessPoint(
    accessPoint,
    &quot;/mnt/efs&quot;,
  ),
  environment: {
    APP_ENV: &quot;production&quot;,
    LOG_CHANNEL: &quot;stderr&quot;,
    DB_CONNECTION: &quot;sqlite&quot;,
    DB_DATABASE: &quot;/mnt/efs/database.sqlite&quot;,
  },
  phpVersion: &quot;8.4&quot;,
});

const api = new apigwv2.HttpApi(this, &quot;BrefHttpApi&quot;, {
  defaultIntegration: new integrations.HttpLambdaIntegration(
    &quot;Integration&quot;,
    backendFn,
  ),
});

new cdk.CfnOutput(this, &quot;ApiUrl&quot;, {
  value: api.url!,
});</code></pre></div>



<div class="wp-block-cocoon-blocks-tab-box-1 blank-box bb-tab bb-check block-box">
<p>URLを <code>CfnOutput</code> で出力するとデプロイ後のアクセスが楽になります。</p>
</div>



<h4 class="wp-block-heading"><span id="toc14">Consoleアプリケーション</span></h4>



<p>Brefには、<code>ConsoleFunction</code> というConstructがあります。これは、artisanコマンドを実行するエンドポイントになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-ts" data-file="lib/construct/artisan.ts" data-lang="TypeScript"><code>new ConsoleFunction(this, &quot;BrefConsoleFunction&quot;, {
  functionName: &quot;ConsoleFunction&quot;,
  handler: &quot;artisan&quot;,
  code: packagePhpCode(&quot;../laravel/&quot;, {
    exclude: [&quot;tests/**&quot;, &quot;var/**&quot;],
  }),
  timeout: cdk.Duration.seconds(28),
  memorySize: 512,
  vpc: vpc,
  filesystem: lambda.FileSystem.fromEfsAccessPoint(
    accessPoint,
    &quot;/mnt/efs&quot;,
  ),
  environment: {
    APP_ENV: &quot;production&quot;,
    LOG_CHANNEL: &quot;stderr&quot;,
    DB_CONNECTION: &quot;sqlite&quot;,
    DB_DATABASE: &quot;/mnt/efs/database.sqlite&quot;,
  },
  phpVersion: &quot;8.4&quot;,
});</code></pre></div>



<div class="wp-block-cocoon-blocks-tab-box-1 blank-box bb-tab bb-check block-box">
<p>artisanコマンドはaws cli経由で実行しますので、<code>functionName</code> を定義しておくと後々便利です。</p>
</div>



<h2 class="wp-block-heading"><span id="toc15">デプロイ・動作確認</span></h2>



<p>デプロイして動作確認してみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>% cdk deploy

(略)

Outputs:
BrefStack.ApiUrl = https://example.execute-api.ap-northeast-1.amazonaws.com/
</code></pre></div>



<p>出力されたURLにアクセスしてみます。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="537" src="https://wptech.kiichiro.work/wp-content/uploads/2025/04/e6f5d404fcf83bb886fcbff5cd5339e9-1024x537.png" alt="Internal Server Error のタイトルで、Database file at path [/mnt/efs/database.sqlite] does not exist. のメッセージが出ている。" class="wp-image-3787" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/04/e6f5d404fcf83bb886fcbff5cd5339e9-1024x537.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/e6f5d404fcf83bb886fcbff5cd5339e9-300x157.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/e6f5d404fcf83bb886fcbff5cd5339e9-768x403.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/e6f5d404fcf83bb886fcbff5cd5339e9-1536x805.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/e6f5d404fcf83bb886fcbff5cd5339e9.png 1618w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>当然、migrationが終わっていないのでエラーが表示されます。ConsoleFunctionを直接実行してmigrationします。aws cliを利用して以下を実行します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>% aws lambda invoke \
     --function-name ConsoleFunction \
     --region ap-northeast-1 \
     --cli-binary-format raw-in-base64-out \
     --payload &#39;&quot;migrate --force&quot;&#39; \
     response.json</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>{
    &quot;StatusCode&quot;: 200,
    &quot;ExecutedVersion&quot;: &quot;$LATEST&quot;
}
</code></pre></div>



<p>Lambdaの実行に問題がなければLaravelの画面が見られるようになるはずです。</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="483" src="https://wptech.kiichiro.work/wp-content/uploads/2025/04/deca5a578e8813d0057e8e4cd7a16ea4-1024x483.png" alt="Laravel 12 の初期画面。" class="wp-image-3790" srcset="https://wptech.kiichiro.work/wp-content/uploads/2025/04/deca5a578e8813d0057e8e4cd7a16ea4-1024x483.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/deca5a578e8813d0057e8e4cd7a16ea4-300x141.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/deca5a578e8813d0057e8e4cd7a16ea4-768x362.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/deca5a578e8813d0057e8e4cd7a16ea4-1536x724.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2025/04/deca5a578e8813d0057e8e4cd7a16ea4.png 1934w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading"><span id="toc16">まとめと感想</span></h2>



<p>本記事では、BrefをCDKで利用してAWS Lambda上でLaravelを動かしてみました。また、低コストでの構築を目指し、最小構成の形を取りました。</p>



<p>BrefはLaravelアプリケーション側で意識することが少ないですので、かなり手軽にできるんじゃないかと思いました。デプロイパイプラインを整えれば、本番環境にも適用できると思います。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PHP Conference Japan 2024 に LT しに行ってきました</title>
		<link>https://wptech.kiichiro.work/42h61ra7k3/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Wed, 25 Dec 2024 15:46:31 +0000</pubDate>
				<category><![CDATA[その他]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3485</guid>

					<description><![CDATA[PHP カンファレンスに参加してきました。現地参加はかなり久々でした。今回は LT に採択されたのでセッションを聞くだけではなく、登壇するという今までとは違った参加の仕方になりました。 目次 見たセッションRustで作る [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>PHP カンファレンスに参加してきました。現地参加はかなり久々でした。<br>今回は LT に採択されたのでセッションを聞くだけではなく、登壇するという今までとは違った参加の仕方になりました。</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-6" checked><label class="toc-title" for="toc-checkbox-6">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">見たセッション</a><ol><li><a href="#toc2" tabindex="0">Rustで作るPHP拡張モジュール：PSR-7ライブラリ編</a></li><li><a href="#toc3" tabindex="0">見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理</a></li></ol></li><li><a href="#toc4" tabindex="0">LTで発表した話</a></li><li><a href="#toc5" tabindex="0">感想</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">見たセッション</span></h2>



<p>当日はオープニングと廣川氏のトークを配信で見ながら電車で向かいました。</p>



<p>現地に到着してからはいくつかセッションを見て回りました。特に印象に残ったものの感想を書いておきます。</p>



<h3 class="wp-block-heading"><span id="toc2">Rustで作るPHP拡張モジュール：PSR-7ライブラリ編</span></h3>




<a rel="noopener" href="https://fortee.jp/phpcon-2024/proposal/6fb6132e-bbff-438c-b3d0-07d903a9cf51" title="Rustで作るPHP拡張モジュール：PSR-7ライブラリ編" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Ffortee.jp%2Fphpcon-2024%2Fproposal%2F6fb6132e-bbff-438c-b3d0-07d903a9cf51?w=160&#038;h=90" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Rustで作るPHP拡張モジュール：PSR-7ライブラリ編</div><div class="blogcard-snippet external-blogcard-snippet">Rustは高いパフォーマンスとメモリ安全性を両立したプログラミング言語で、最近ではLinuxカーネルの開発に一部取り入れられたことでも話題になるなど、人気の高い言語の一つです。そのRustで、PHPの拡張モジュールを作ることができるのをご存...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://fortee.jp/phpcon-2024/proposal/6fb6132e-bbff-438c-b3d0-07d903a9cf51" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">fortee.jp</div></div></div></div></a>



<p>Rust には興味はあったものの、きっかけが掴めず今日までいました。PHP に関することで使えるならやってみようかなと思いました。</p>



<h3 class="wp-block-heading"><span id="toc3">見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理</span></h3>




<a rel="noopener" href="https://fortee.jp/phpcon-2024/proposal/6dadacb3-51b7-4a71-910a-f5f71f75e1e3" title="見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Ffortee.jp%2Fphpcon-2024%2Fproposal%2F6dadacb3-51b7-4a71-910a-f5f71f75e1e3?w=160&#038;h=90" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理</div><div class="blogcard-snippet external-blogcard-snippet">PHP 8.4で追加された `pg_result_memory_size()` は、SQL実行結果の中でも `memory_get_usage()` に計上されない隠れたメモリ使用量を可視化します。特に大量データ処理時のメモリ不足リスクを軽...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://fortee.jp/phpcon-2024/proposal/6dadacb3-51b7-4a71-910a-f5f71f75e1e3" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">fortee.jp</div></div></div></div></a>



<p>普段、意識することのない PHP のメモリ管理のお話で非常に勉強になりました。php-src へのコントリビューションのヒントも得られました。</p>



<h2 class="wp-block-heading"><span id="toc4">LTで発表した話</span></h2>



<p>LT させてもらいました。</p>



<figure class="wp-block-embed is-type-rich is-provider-speaker-deck wp-block-embed-speaker-deck wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Opcodeを読んでいたら何故かphp-srcを読んでいた話" id="talk_frame_1299305" class="speakerdeck-iframe" src="//speakerdeck.com/player/379718951c7a4a0885023f48aaf93005" width="1320" height="742" style="aspect-ratio:1320/742; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe>
</div></figure>



<p>LT に関しては、プロポーザル提出する前から果たしてこのネタは面白いのか？、技術的に間違ったことを書いて無いか？、という葛藤を100週くらいしており、応募のボタンをなかなか押せないでいました。<br>最終的には、悩むのは採択されてからにしようという考えに至り、締切ギリギリにて提出が完了しました。無事採択されてからは夜も眠れないほど悩みました。</p>



<p>ただ、そんなことは杞憂でしたね。当日の X のタイムラインでは多くの方に反応をいただけていたようです。</p>



<figure class="wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter"><div class="wp-block-embed__wrapper">
<blockquote class="twitter-tweet" data-width="550" data-dnt="true"><p lang="ja" dir="ltr">LTあんまり自信なかったけど、反応が結構あったのでやって良かったなと</p>&mdash; しおたろ (@muraSHOTARO) <a href="https://twitter.com/muraSHOTARO/status/1870791522716963322?ref_src=twsrc%5Etfw">December 22, 2024</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div></figure>



<p>月並みな感想ですが、やってよかったです。</p>



<p>今回のネタは int の型キャストと intval だけでしたが、Opcode の最適化はそこかしこで行われているので、自分の調べた範囲のものはいずれポストしようと思います。</p>



<h2 class="wp-block-heading"><span id="toc5">感想</span></h2>



<p>やっぱ現地参加は良かったです。新しいつながりもできますし、あの空気感はオンラインでは味わえないです。来年も何かしら現地参加してみようと思います。</p>



<p>あと、ほぼ最初から最後までいたのですが、午前中に張り切りすぎて午後は集中力が切れてました。。体力をもっとつけようと思いました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】Laravel SanctumがCSRFトークンの検証で何やってるかをちゃんと理解する</title>
		<link>https://wptech.kiichiro.work/760fab7xr3/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Thu, 19 Dec 2024 14:41:21 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[コードリーディング]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3254</guid>

					<description><![CDATA[この記事は Laravel Advent Calendar 2024 19日目の投稿です。 Laravel - Qiita Advent Calendar 2024 - QiitaCalendar page for Qi [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>この記事は Laravel Advent Calendar 2024 19日目の投稿です。</p>




<a rel="noopener" href="https://qiita.com/advent-calendar/2024/laravel" title="Laravel - Qiita Advent Calendar 2024 - Qiita" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2024%2Flaravel?w=160&#038;h=90" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Laravel - Qiita Advent Calendar 2024 - Qiita</div><div class="blogcard-snippet external-blogcard-snippet">Calendar page for Qiita Advent Calendar 2024 regarding Laravel.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://qiita.com/advent-calendar/2024/laravel" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">qiita.com</div></div></div></div></a>



<p>Laravel Sanctum が簡単な認証システムを提供してくれるのは良く知られているんですが、認証に関する面倒なことを考えずに済む程度には抽象化されてしまっていて、裏で何をやっているのかを考えたことがありませんでした。</p>



<p>今回はそんな Laravel Sanctum が CSRF トークンの検証で何をやっているのかを追っていこうと思います。</p>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box information-box">
<p>Laravel Sanctum のインストールについては公式ドキュメントに記載があります。詳細はそちらを御覧ください。</p>




<a rel="noopener" href="https://laravel.com/docs/11.x/sanctum#installation" title="Laravel Sanctum - Laravel 11.x - The PHP Framework For Web Artisans" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Flaravel.com%2Fdocs%2F11.x%2Fsanctum%23installation?w=160&#038;h=90" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Laravel Sanctum - Laravel 11.x - The PHP Framework For Web Artisans</div><div class="blogcard-snippet external-blogcard-snippet">Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravel.com/docs/11.x/sanctum#installation" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravel.com</div></div></div></div></a>
</div>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-8" checked><label class="toc-title" for="toc-checkbox-8">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">Middleware</a></li><li><a href="#toc2" tabindex="0">VerifyCsrfToken</a><ol><li><a href="#toc3" tabindex="0">CSRFトークンの検証</a></li><li><a href="#toc4" tabindex="0">CSRFトークンの付与</a></li></ol></li><li><a href="#toc5" tabindex="0">まとめ</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">Middleware</span></h2>



<p>Sanctum インストール時に <code>bootstrap/app.php</code> に以下の記述を追加したと思います。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="bootstrap/app.php" data-lang="PHP"><code>-&gt;withMiddleware(function (Middleware $middleware) {
    $middleware-&gt;statefulApi();
})</code></pre></div>



<p>これは、特定のミドルウェアの有効化フラグを true にします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Foundation/Configuration/Middleware.php" data-lang="PHP"><code>/**
 * Indicate that Sanctum&#39;s frontend state middleware should be enabled.
 *
 * @return $this
 */
public function statefulApi()
{
    $this-&gt;statefulApi = true;

    return $this;
}</code></pre></div>



<p>ここが true になっていると、api ミドルウェアグループを利用してるエンドポイントは EnsureFrontendRequestsAreStateful というミドルウェアを通るようになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Foundation/Configuration/Middleware.php" data-lang="PHP" data-line="19"><code>/**
 * Get the middleware groups.
 *
 * @return array
 */
public function getMiddlewareGroups()
{
    $middleware = [
        &#39;web&#39; =&gt; array_values(array_filter([
            \Illuminate\Cookie\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            $this-&gt;authenticatedSessions ? &#39;auth.session&#39; : null,
        ])),
        &#39;api&#39; =&gt; array_values(array_filter([
            $this-&gt;statefulApi ? \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class : null,
            $this-&gt;apiLimiter ? &#39;throttle:&#39;.$this-&gt;apiLimiter : null,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ])),
    ];
(省略)
}</code></pre></div>



<p>EnsureFrontendRequestsAreStateful では複数のミドルウェアを通していますが、ほとんどが セッションにまつわるミドルウェアで web ミドルウェアグループで定義されているミドルウェアとほぼ同じです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Laravel/Sanctum/Http/Middleware/EnsureFrontendRequestsAreStateful.php" data-lang="PHP" data-line="13,32"><code>/**
 * Handle the incoming requests.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  callable  $next
 * @return \Illuminate\Http\Response
 */
public function handle($request, $next)
{
    $this-&gt;configureSecureCookieSessions();

    return (new Pipeline(app()))-&gt;send($request)-&gt;through(
        static::fromFrontend($request) ? $this-&gt;frontendMiddleware() : []
    )-&gt;then(function ($request) use ($next) {
        return $next($request);
    });
}

(省略)

/**
 * Get the middleware that should be applied to requests from the &quot;frontend&quot;.
 *
 * @return array
 */
protected function frontendMiddleware()
{
    $middleware = array_values(array_filter(array_unique([
        config(&#39;sanctum.middleware.encrypt_cookies&#39;, \Illuminate\Cookie\Middleware\EncryptCookies::class),
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        config(&#39;sanctum.middleware.validate_csrf_token&#39;, config(&#39;sanctum.middleware.verify_csrf_token&#39;, \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class)),
        config(&#39;sanctum.middleware.authenticate_session&#39;),
    ])));

    array_unshift($middleware, function ($request, $next) {
        $request-&gt;attributes-&gt;set(&#39;sanctum&#39;, true);

        return $next($request);
    });

    return $middleware;
}</code></pre></div>



<p>その中から今回見ていくのは、VerifyCsrfToken になります。</p>



<h2 class="wp-block-heading"><span id="toc2">VerifyCsrfToken</span></h2>



<p>では、VerifyCsrfToken を見ていきます。</p>



<h3 class="wp-block-heading"><span id="toc3">CSRFトークンの検証</span></h3>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php" data-lang="PHP" data-line="13,14,15,16"><code>/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 *
 * @throws \Illuminate\Session\TokenMismatchException
 */
public function handle($request, Closure $next)
{
    if (
        $this-&gt;isReading($request) ||
        $this-&gt;runningUnitTests() ||
        $this-&gt;inExceptArray($request) ||
        $this-&gt;tokensMatch($request)
    ) {
        return tap($next($request), function ($response) use ($request) {
            if ($this-&gt;shouldAddXsrfTokenCookie()) {
                $this-&gt;addCookieToResponse($request, $response);
            }
        });
    }

    throw new TokenMismatchException(&#39;CSRF token mismatch.&#39;);
}</code></pre></div>



<p>VerifyCsrfToken では、まず検証の対象外かどうかを判定するために以下を走査しています。</p>



<ul class="wp-block-list">
<li>HTTP リクエストのメソッドが HEAD, GET, OPTIONS のいずれかかどうか</li>



<li>コンソールで実行されているかどうか
<ul class="wp-block-list">
<li><code>APP_RUNNING_IN_CONSOLE</code></li>



<li>SAPI が cli または phpdbg</li>
</ul>
</li>



<li>ユニットテストで実行されているかどうか
<ul class="wp-block-list">
<li><code>APP_ENV</code> が testing</li>
</ul>
</li>



<li>除外対象 URI かどうか
<ul class="wp-block-list">
<li><a href="https://laravel.com/docs/11.x/csrf#csrf-excluding-uris">Excluding URIs From CSRF Protection</a></li>
</ul>
</li>
</ul>



<p>検証の対象だった場合は、リクエストされた CSRF トークンの整合性を検証します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php" data-lang="PHP"><code>/**
 * Determine if the session and input CSRF tokens match.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return bool
 */
protected function tokensMatch($request)
{
    $token = $this-&gt;getTokenFromRequest($request);

    return is_string($request-&gt;session()-&gt;token()) &&
            is_string($token) &&
            hash_equals($request-&gt;session()-&gt;token(), $token);
}</code></pre></div>



<p>ここでは、まずリクエストからトークンを取得します。フォームからのリクエストなどでボディに、<code>_token</code> があればそれを利用し、なければヘッダから <code>X-CSRF-TOKEN</code> または <code>X-XSRF-TOKEN</code> を取得します。<br>そして、そのトークンとサーバー側のセッションストレージに保存されているトークンを比較しています。この検証に失敗すると、TokenMismatchException が投げられ、ステータスコード 419 でレスポンスが返される、ということになります。</p>



<h3 class="wp-block-heading"><span id="toc4">CSRFトークンの付与</span></h3>



<p>CSRF トークンは Set-Cookie レスポンスヘッダで付与されます。これも VerifyCsrfToken でやっています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php" data-lang="PHP" data-line="20,45,62"><code>/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 *
 * @throws \Illuminate\Session\TokenMismatchException
 */
public function handle($request, Closure $next)
{
    if (
        $this-&gt;isReading($request) ||
        $this-&gt;runningUnitTests() ||
        $this-&gt;inExceptArray($request) ||
        $this-&gt;tokensMatch($request)
    ) {
        return tap($next($request), function ($response) use ($request) {
            if ($this-&gt;shouldAddXsrfTokenCookie()) {
                $this-&gt;addCookieToResponse($request, $response);
            }
        });
    }

    throw new TokenMismatchException(&#39;CSRF token mismatch.&#39;);
}

(省略)

/**
 * Add the CSRF token to the response cookies.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Symfony\Component\HttpFoundation\Response  $response
 * @return \Symfony\Component\HttpFoundation\Response
 */
protected function addCookieToResponse($request, $response)
{
    $config = config(&#39;session&#39;);

    if ($response instanceof Responsable) {
        $response = $response-&gt;toResponse($request);
    }

    $response-&gt;headers-&gt;setCookie($this-&gt;newCookie($request, $config));

    return $response;
}

(省略)

/**
 * Create a new &quot;XSRF-TOKEN&quot; cookie that contains the CSRF token.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  array  $config
 * @return \Symfony\Component\HttpFoundation\Cookie
 */
protected function newCookie($request, $config)
{
    return new Cookie(
        &#39;XSRF-TOKEN&#39;,
        $request-&gt;session()-&gt;token(),
        $this-&gt;availableAt(60 * $config[&#39;lifetime&#39;]),
        $config[&#39;path&#39;],
        $config[&#39;domain&#39;],
        $config[&#39;secure&#39;],
        false,
        false,
        $config[&#39;same_site&#39;] ?? null,
        $config[&#39;partitioned&#39;] ?? false
    );
}
</code></pre></div>



<p>ここでは、<code>XSRF-TOKEN</code> でセッショントークンを返すようにしています。</p>



<h2 class="wp-block-heading"><span id="toc5">まとめ</span></h2>



<ul class="wp-block-list">
<li>Laravel Sanctum インストールの手順を行うと、api ミドルウェアグループでセッションの検証ができるようになる</li>



<li>CSRF トークンの検証は、VerifyCsrfToken ミドルウェアで行っている
<ul class="wp-block-list">
<li>対象でなければ検証しない</li>



<li>対象であればトークンとセッションデータを比較する</li>
</ul>
</li>



<li>CSRF トークンは Set-Cookie レスポンスヘッダに <code>XSRF-TOKEN</code> を付与している</li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【WordPress】ホストネットワーキングモードでWP-Cronをちゃんと動かす</title>
		<link>https://wptech.kiichiro.work/793kyf6xrh/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Wed, 18 Sep 2024 15:46:05 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3256</guid>

					<description><![CDATA[先日、Docker Desktop でもホストネットワーキングモードがサポートされたようです。 Docker Desktop内のコンテナに対して「localhost」でアクセス可能に、WSL2のストレージ領域を自動で縮小 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>先日、Docker Desktop でもホストネットワーキングモードがサポートされたようです。</p>




<a rel="noopener" href="https://www.publickey1.jp/blog/24/docker_desktoplocalhostwsl2docker_desktop_434.html" title="Docker Desktop内のコンテナに対して「localhost」でアクセス可能に、WSL2のストレージ領域を自動で縮小など新機能、Docker Desktop 4.34正式リリース" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://www.publickey1.jp/2024/docker434ga01.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Docker Desktop内のコンテナに対して「localhost」でアクセス可能に、WSL2のストレージ領域を自動で縮小など新機能、Docker Desktop 4.34正式リリース</div><div class="blogcard-snippet external-blogcard-snippet">Docker社は、WindowsやMac、Linuxに手軽にDockerコンテナ環境を導入し利用できるソフトウェアであるDocker Desktopの最新版「Docker Desktop 4.34」正式版のリリースを発表しました。 Dock...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://www.publickey1.jp/blog/24/docker_desktoplocalhostwsl2docker_desktop_434.html" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.publickey1.jp</div></div></div></div></a>



<p>どのようなときにホストネットワーキングモードの恩恵を受けられるか考えていたのですが、ローカル環境かつ Nginx + PHP-FPM 構成で WP-Cron を使った時に良さそうだなと思ったのでやってみました。</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-10" checked><label class="toc-title" for="toc-checkbox-10">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">WP-Cron が機能しない問題</a></li><li><a href="#toc2" tabindex="0">やってみた</a></li><li><a href="#toc3" tabindex="0">まとめと感想</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">WP-Cron が機能しない問題</span></h2>



<p>ローカル環境で Nginx + PHP-FPM 構成で WordPress を動かそうとすると、WP-Cron が機能しなくなるというのは、誰もが一度は通った道かと思います。</p>



<p>WP-Cron が機能しなくなる理由を簡単に説明すると、以下のような流れになります。</p>



<ol class="wp-block-list">
<li><strong>WP-Cron は WordPress へのリクエストによってトリガーされる</strong><br>WP-Cron はページへのアクセスがあった際にスケジュールを確認し、必要があれば <code>site_url</code> 配下のエンドポイントへ HTTP リクエストを送信します。</li>



<li><strong>site_url がローカル環境では localhost になる</strong><br>ローカル環境で WordPress を実行しているため、<code>site_url</code> は <code>http://localhost</code> になることが多いです。そのため、WP-Cron は <code>localhost</code> に対してリクエストを送信することになります。</li>



<li><strong>PHP-FPM コンテナで localhost にリクエストをすると、自分自身に送信される</strong><br>Docker コンテナで <code>localhost</code> はコンテナ自身を示します。</li>



<li><strong>PHP-FPM コンテナには HTTP サーバーは開かれていない</strong><br>PHP-FPM コンテナは HTTP リクエストを受け付けるポート (通常は 80 番) が開かれていません。よって、リクエストが失敗し、WP-Cron が実行できなくなります。</li>
</ol>



<p>要するに、<code>localhost</code> が示す先がコンテナ自身になってしまうということが原因ですので、WordPress を実行するサーバーの IP アドレスにドメインが関連付けられている場合 (本番環境など) では発生し得ません。</p>



<p>WP-Cron が機能していないことは管理画面の通知から窺い知ることが出来ます。<a href="https://ja.wordpress.org/plugins/wp-crontrol/">WP Crontrol</a> をインストールした環境であれば設定画面で確認出来ます。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="195" src="https://wptech.kiichiro.work/wp-content/uploads/2024/09/3369aed6f02f6b45398f6124399a98a1-1024x195.png" alt="WP Crontrol の通知で、WP-Cron が機能しないことを示す「cURL error 7: Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server。」が表示されている" class="wp-image-3309" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/09/3369aed6f02f6b45398f6124399a98a1-1024x195.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/3369aed6f02f6b45398f6124399a98a1-300x57.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/3369aed6f02f6b45398f6124399a98a1-768x146.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/3369aed6f02f6b45398f6124399a98a1-1536x292.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/3369aed6f02f6b45398f6124399a98a1-2048x389.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>cURL error 7: Failed to connect to localhost port 80 after 0 ms: Couldn&#39;t connect to server。</code></pre></div>



<p>この事象の回避方法としては、HTTP リクエストを投げる先を <code>cron_request</code> フィルターフックで指定しているので、<code>host.docker.internal</code> などに書き換えるなどがあります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="cron.php" data-lang="PHP" data-line="4"><code>$cron_request = apply_filters(
	&#39;cron_request&#39;,
	array(
		&#39;url&#39;  =&gt; add_query_arg( &#39;doing_wp_cron&#39;, $doing_wp_cron, site_url( &#39;wp-cron.php&#39; ) ),
		&#39;key&#39;  =&gt; $doing_wp_cron,
		&#39;args&#39; =&gt; array(
			&#39;timeout&#39;   =&gt; 0.01,
			&#39;blocking&#39;  =&gt; false,
			/** This filter is documented in wp-includes/class-wp-http-streams.php */
			&#39;sslverify&#39; =&gt; apply_filters( &#39;https_local_ssl_verify&#39;, false ),
		),
	),
	$doing_wp_cron
);</code></pre></div>



<p>ただ、ローカル環境のみで発生する事象に対して手を加えるのは、若干の気持ち悪さが残ります。</p>



<p>Docker のホストネットワーキングモードを使用すれば、<code>localhost</code> はホストを指すことになるので、上記の問題が解消しそうです。</p>



<h2 class="wp-block-heading"><span id="toc2">やってみた</span></h2>



<p>まずは、ホストネットワーキングモードを Docker Desktop 側で有効化します。手順は以下を参考にしました。</p>




<a rel="noopener" href="https://docs.docker.com/engine/network/drivers/host/#docker-desktop" title="Host network driver" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://docs.docker.com/images/thumbnail.webp" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Host network driver</div><div class="blogcard-snippet external-blogcard-snippet">All about exposing containers on the Docker host&#039;s network</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://docs.docker.com/engine/network/drivers/host/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">docs.docker.com</div></div></div></div></a>



<p>次に、Docker で Nginx + PHP-FPM + WordPress の環境を作ってみます。compose.yaml は以下です。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="compose.yaml" data-line="11,21,27"><code>services:
   db:
     image: mysql:8.0
     volumes:
       - db_data:/var/lib/mysql
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress
     network_mode: &quot;host&quot;

   nginx:
    image: nginx:1.27.1-bookworm
    volumes:
      - ./default.conf:/etc/nginx/conf.d/default.conf
    volumes_from:
      - wordpress-fpm
    depends_on:
      - wordpress-fpm
    network_mode: &quot;host&quot;

   wordpress-fpm:
     image: wordpress:6.6.2-php8.1-fpm
     depends_on:
       - db
     network_mode: &quot;host&quot;

volumes:
    db_data:
</code></pre></div>



<p>ここで <code>network_mode: "host"</code> を指定することでホストネットワーキングモードが利用できます。</p>



<p>Nginx の default.conf は以下を用意しました。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="default.conf" data-line="23"><code>server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    root /var/www/html;
    index index.php;

    location / {
        index  index.php;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        fastcgi_pass   localhost:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}</code></pre></div>



<p>上記のファイルを作成出来たら <code>docker compose up</code> で立ち上げ、ブラウザから <code>localhost</code> にアクセスします。あとは適当にポチポチと設定をしてログインします。</p>



<p>データベースの設定での注意点として、ホスト名は <code>localhost</code> ではなく <code>127.0.0.1</code> を指定してください。</p>




<a rel="noopener" href="https://dev.mysql.com/doc/refman/8.0/ja/connecting.html" title="MySQL :: MySQL 8.0 &#12522;&#12501;&#12449;&#12524;&#12531;&#12473;&#12510;&#12491;&#12517;&#12450;&#12523; :: 4.2.4 &#12467;&#12510;&#12531;&#12489;&#12458;&#12503;&#12471;&#12519;&#12531;&#12434;&#20351;&#29992;&#12375;&#12383; MySQL Server &#12408;&#12398;&#25509;&#32154;" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F8.0%2Fja%2Fconnecting.html?w=160&#038;h=90" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">MySQL :: MySQL 8.0 &#12522;&#12501;&#12449;&#12524;&#12531;&#12473;&#12510;&#12491;&#12517;&#12450;&#12523; :: 4.2.4 &#12467;&#12510;&#12531;&#12489;&#12458;&#12503;&#12471;&#12519;&#12531;&#12434;&#20351;&#29992;&#12375;&#12383; MySQL Server &#12408;&#12398;&#25509;&#32154;</div><div class="blogcard-snippet external-blogcard-snippet"></div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://dev.mysql.com/doc/refman/8.0/ja/connecting.html" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">dev.mysql.com</div></div></div></div></a>



<p>ログインしてダッシュボードまで辿り着いたら、WP-Cron の確認をします。WP Crontrol をインストールして設定画面を見てみると「cURL error 7: ~」のメッセージが表示されていないことが確認出来ます。</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="173" data-id="3336" src="https://wptech.kiichiro.work/wp-content/uploads/2024/09/9bee04000217b9bc26441371a8ca5acd-1024x173.png" alt="WP Crontrol の Cron イベント画面でエラー通知が表示されていない" class="wp-image-3336" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/09/9bee04000217b9bc26441371a8ca5acd-1024x173.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/9bee04000217b9bc26441371a8ca5acd-300x51.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/9bee04000217b9bc26441371a8ca5acd-768x130.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/9bee04000217b9bc26441371a8ca5acd-1536x260.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/9bee04000217b9bc26441371a8ca5acd-2048x347.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p>一覧から適当なイベントの「今すぐ実行」をクリックすると通知が表示され、正常に実行されたことが確認出来ます。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="187" src="https://wptech.kiichiro.work/wp-content/uploads/2024/09/6f9e18e0d3dee9917f96a7c456ae717b-1024x187.png" alt="WP Crontrol の Cron イベント画面で正常終了した通知メッセージが表示されている" class="wp-image-3337" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/09/6f9e18e0d3dee9917f96a7c456ae717b-1024x187.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/6f9e18e0d3dee9917f96a7c456ae717b-300x55.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/6f9e18e0d3dee9917f96a7c456ae717b-768x141.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/6f9e18e0d3dee9917f96a7c456ae717b-1536x281.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2024/09/6f9e18e0d3dee9917f96a7c456ae717b-2048x375.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading"><span id="toc3">まとめと感想</span></h2>



<p>Docker Desktop でホストネットワーキングモードがサポートされたと聞いたときに真っ先にこのネタが浮かびましたが、一般的には安定感の無い WP-Cron をここまでして使いたいモチベーションは無いだろうなとも思いました。個人的には長年抱え続けてきた気持ち悪さが解決したので満足度は高いです。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【WordPress】ACFのJavaScript APIを使ってselect2フィールドをカスタマイズする</title>
		<link>https://wptech.kiichiro.work/629mgaoh70/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Tue, 02 Jul 2024 08:43:24 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[コードリーディング]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3117</guid>

					<description><![CDATA[WordPress の カスタムフィールドのプラグインである ACF (Advanced Custom Fields) には、多数のフックや JavaScript API が用意されています。 ACF &#124; Resourc [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>WordPress の カスタムフィールドのプラグインである ACF (Advanced Custom Fields) には、多数のフックや JavaScript API が用意されています。</p>




<a rel="noopener" href="https://www.advancedcustomfields.com/resources" title="ACF | Resources, Documentation, API, How to &amp; Tutorial Articles" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://www.advancedcustomfields.com/wp-content/uploads/2024/11/acf-social-202411.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">ACF | Resources, Documentation, API, How to & Tutorial Articles</div><div class="blogcard-snippet external-blogcard-snippet">Discover code, documentation and ideas in this comprehensive resource section. Find everything from Getting Started, Fie...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://www.advancedcustomfields.com/resources/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.advancedcustomfields.com</div></div></div></div></a>




<a rel="noopener" href="https://www.advancedcustomfields.com/resources/javascript-api" title="ACF | JavaScript API" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://www.advancedcustomfields.com/wp-content/uploads/2024/11/acf-social-202411.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">ACF | JavaScript API</div><div class="blogcard-snippet external-blogcard-snippet">Learn how to use ACF&#039;s JavaScript library including functions, actions and filters and models.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://www.advancedcustomfields.com/resources/javascript-api/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.advancedcustomfields.com</div></div></div></div></a>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-12" checked><label class="toc-title" for="toc-checkbox-12">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">やること</a></li><li><a href="#toc2" tabindex="0">準備</a></li><li><a href="#toc3" tabindex="0">コーディング</a></li><li><a href="#toc4" tabindex="0">まとめと感想</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">やること</span></h2>



<p>今回は、JavaScript API とフックの組み合わせで select2 フィールドをカスタマイズしてみます。具体的には、投稿のプルダウンをラジオボタンで選択したカテゴリーのみに絞って表示させる、ということをやってみます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="869" height="609" src="https://wptech.kiichiro.work/wp-content/uploads/2024/06/b369a3c9b4aec3fe3c4118709b3019ae.png" alt="カテゴリーのラジオボタンと投稿オブジェクトのプルダウンが表示されている。ラジオボタンで選択したカテゴリーの投稿だけプルダウンに表示するようにする、という説明が入っている" class="wp-image-3122" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/06/b369a3c9b4aec3fe3c4118709b3019ae.png 869w, https://wptech.kiichiro.work/wp-content/uploads/2024/06/b369a3c9b4aec3fe3c4118709b3019ae-300x210.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2024/06/b369a3c9b4aec3fe3c4118709b3019ae-768x538.png 768w" sizes="(max-width: 869px) 100vw, 869px" /></figure>



<h2 class="wp-block-heading"><span id="toc2">準備</span></h2>



<p>ACF でカテゴリーのラジオボタンと投稿のプルダウンを用意します。表示する条件は固定ページにしておきます。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="224" src="https://wptech.kiichiro.work/wp-content/uploads/2024/07/95bee59c1204c3775aeeee29604c35f0-1024x224.png" alt="ACFのフィールド。カテゴリーのラジオボタンと投稿の投稿オブジェクト" class="wp-image-3128" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/07/95bee59c1204c3775aeeee29604c35f0-1024x224.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/95bee59c1204c3775aeeee29604c35f0-300x66.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/95bee59c1204c3775aeeee29604c35f0-768x168.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/95bee59c1204c3775aeeee29604c35f0.png 1436w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>このときの各フィールドのキー (<code>field_667d1f74494c8</code> や <code>field_667d1d6343e57</code>) を控えておきます。</p>



<p>また、初期データとして、投稿とカテゴリーをいくつか用意しておきます。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="168" src="https://wptech.kiichiro.work/wp-content/uploads/2024/07/23f6e6de9d5fd3c97cdbf4f29655c3fa-1024x168.png" alt="固定ページ編集ページで表示されるカテゴリーと投稿のカスタムフィールド" class="wp-image-3131" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/07/23f6e6de9d5fd3c97cdbf4f29655c3fa-1024x168.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/23f6e6de9d5fd3c97cdbf4f29655c3fa-300x49.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/23f6e6de9d5fd3c97cdbf4f29655c3fa-768x126.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/23f6e6de9d5fd3c97cdbf4f29655c3fa-1536x252.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/23f6e6de9d5fd3c97cdbf4f29655c3fa.png 1906w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>投稿のプルダウンをクリックすると、全ての投稿が選択肢として表示されます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="671" height="307" src="https://wptech.kiichiro.work/wp-content/uploads/2024/07/ac5e711ec26fce716af67d234a591730.png" alt="投稿のプルダウンクリック時に全ての投稿が選択肢として表示されている" class="wp-image-3135" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/07/ac5e711ec26fce716af67d234a591730.png 671w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/ac5e711ec26fce716af67d234a591730-300x137.png 300w" sizes="(max-width: 671px) 100vw, 671px" /></figure>



<h2 class="wp-block-heading"><span id="toc3">コーディング</span></h2>



<p>select2 フィールドのプルダウンはクリックの都度、Ajax 通信で表示する選択肢を取得しています。この Ajax のリクエストに介入する JavaScript のフックに <code>select2_ajax_data</code> というのが用意されています。</p>




<a rel="noopener" href="https://www.advancedcustomfields.com/resources/javascript-api/#filters-select2_ajax_data" title="ACF | JavaScript API" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://www.advancedcustomfields.com/wp-content/uploads/2024/11/acf-social-202411.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">ACF | JavaScript API</div><div class="blogcard-snippet external-blogcard-snippet">Learn how to use ACF&#039;s JavaScript library including functions, actions and filters and models.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://www.advancedcustomfields.com/resources/javascript-api/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.advancedcustomfields.com</div></div></div></div></a>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-file="acf-input.js" data-lang="JavaScript" data-line="25"><code>getAjaxData: function (params) {
  // vars
  var ajaxData = {
    action: this.get(&#39;ajaxAction&#39;),
    s: params.term || &#39;&#39;,
    paged: params.page || 1
  };

  // field helper
  var field = this.get(&#39;field&#39;);
  if (field) {
    ajaxData.field_key = field.get(&#39;key&#39;);
    if (field.get(&#39;nonce&#39;)) {
      ajaxData.nonce = field.get(&#39;nonce&#39;);
    }
  }

  // callback
  var callback = this.get(&#39;ajaxData&#39;);
  if (callback) {
    ajaxData = callback.apply(this, [ajaxData, params]);
  }

  // filter
  ajaxData = acf.applyFilters(&#39;select2_ajax_data&#39;, ajaxData, this.data, this.$el, field || false, this);

  // return
  return acf.prepareForAjax(ajaxData);
},</code></pre></div>



<p>このフックを使ってラジオボタンで選択されたカテゴリーの情報を POST に追加するようにします。適当な JS ファイルに以下の記述をします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-file="acf-js-api-usage.js" data-lang="JavaScript"><code>// 素の JS
acf.add_filter(&#39;select2_ajax_data&#39;, function (data, args, $input, field, instance) {
    if (&#39;field_667d1d6343e57&#39; !== data.field_key) {
        // 投稿のプルダウンでなければ何もしない
        return data;
    }

    // カテゴリーのラジオボタンで選択された value を取得する
    const selectedCat = document.querySelector(&#39;input[name=&quot;acf[field_667d1f74494c8]&quot;]:checked&#39;);
    if (selectedCat) {
        data.cat = selectedCat.value;
    }
    return data;
});

// jQuery
jQuery(document).ready(function ($) {
    acf.add_filter(&#39;select2_ajax_data&#39;, function (data, args, $input, field, instance) {
        if (&#39;field_667d1d6343e57&#39; !== data.field_key) {
            return data;
        }
        const $selectedCat = $(&#39;input[name=&quot;acf[field_667d1f74494c8]&quot;]:checked&#39;);
        if ($selectedCat) {
            data.cat = $selectedCat.val();
        }
        return data;
    });
});</code></pre></div>



<p>フィールド作成時に控えておいたキーはユニークになっているので、セレクタとして使用しています。</p>



<p>次に、PHP 側のフックを使って、作成した JS ファイルの enqueue を行います。注意点として、ACF の JavaScript API を利用するには、ハンドル名 <code>acf-input</code> があらかじめ読み込まれている必要があるので、引数に追加するのを忘れないようにします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>add_action(&#39;admin_enqueue_scripts&#39;, function ($hook_suffix) {
    if (&#39;post.php&#39; === $hook_suffix || &#39;post-new.php&#39; === $hook_suffix) {
        wp_register_script(&#39;acf-js-api-usage&#39;, plugin_dir_url(__FILE__) . &#39;/acf-js-api-usage.js&#39;, [&#39;acf-input&#39;]);
        wp_enqueue_script(&#39;acf-js-api-usage&#39;);
    }
});</code></pre></div>



<p>PHP 側では POST したカテゴリー情報をハンドリングする処理も必要です。ACF で、<code>acf/fields/post_object/query/key={$key}</code> というフックが用意されているので、これを利用します。</p>




<a rel="noopener" href="https://www.advancedcustomfields.com/resources/acf-fields-post_object-query" title="ACF | acf/fields/post_object/query" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://www.advancedcustomfields.com/wp-content/uploads/2024/11/acf-social-202411.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">ACF | acf/fields/post_object/query</div><div class="blogcard-snippet external-blogcard-snippet">Filters the $args used to query posts in the Post Object field.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://www.advancedcustomfields.com/resources/acf-fields-post_object-query/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.advancedcustomfields.com</div></div></div></div></a>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>add_filter(&#39;acf/fields/post_object/query/key=field_667d1d6343e57&#39;, function($args, $field, $post_id) {
    $args[&#39;cat&#39;] = $_POST[&#39;cat&#39;];
    return $args;
}, 10, 3);</code></pre></div>



<p>このフィルターフックで返却する <code>$args</code> は最終的に WP_Query の引数になります。今回はカテゴリーで絞り込むので、POST した値を <code>$args['cat']</code> に代入します。</p>



<p>画面を確認してみると、ラジオボタンで選択したカテゴリーの投稿のみが表示されていることがわかります。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="554" height="294" src="https://wptech.kiichiro.work/wp-content/uploads/2024/07/d29dc7be02d3d11f0cd2b7f69423eb9b.png" alt="" class="wp-image-3157" srcset="https://wptech.kiichiro.work/wp-content/uploads/2024/07/d29dc7be02d3d11f0cd2b7f69423eb9b.png 554w, https://wptech.kiichiro.work/wp-content/uploads/2024/07/d29dc7be02d3d11f0cd2b7f69423eb9b-300x159.png 300w" sizes="(max-width: 554px) 100vw, 554px" /></figure>



<h2 class="wp-block-heading"><span id="toc4">まとめと感想</span></h2>



<p>今回、ACF のコードを数年ぶりに読みました。私にとって、WordPress のプラグインの中では読みやすい部類だと感じました。ドキュメントもしっかりメンテされている (と思っている) ので、迷子になることもなかったです。</p>



<p>ACF の JavaScript API とフックは、設定画面では出来ないようなフィールドの複雑なカスタマイズやフィールドの表示条件を動的に変更することが出来るので、わりと便利な機能だと思っています。ただ、認知度が低い気がします。日本語の記事もほとんど見当たらなかったですし。だいぶニッチな機能なので仕方ないと言えばそうなのですが。。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】laravel-modulesでmiddlewareの登録をする</title>
		<link>https://wptech.kiichiro.work/90uszwt39n/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Tue, 25 Jun 2024 13:34:06 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[コードリーディング]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=3072</guid>

					<description><![CDATA[Laravel で手っ取り早くモジュラモノリスやるときは laravel-modules を良く使います。 GitHub - nWidart/laravel-modules: Module Management In L [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Laravel で手っ取り早くモジュラモノリスやるときは laravel-modules を良く使います。</p>




<a rel="noopener" href="https://github.com/nWidart/laravel-modules" title="GitHub - nWidart/laravel-modules: Module Management In Laravel" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://opengraph.githubassets.com/06f58c73de0840869955726bca7cd951c8af998c95dad257fab9389a8fe6b1d8/nWidart/laravel-modules" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">GitHub - nWidart/laravel-modules: Module Management In Laravel</div><div class="blogcard-snippet external-blogcard-snippet">Module Management In Laravel. Contribute to nWidart/laravel-modules development by creating an account on GitHub.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://github.com/nWidart/laravel-modules" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">github.com</div></div></div></div></a>



<p>Modules 配下に各モジュールを配置しているためファイルがモジュール毎にまとまっており、内部設計的にはシンプルでわかりやすいと思います。ただし、モジュール間でファイルを相互に参照出来るので、依存関係の検証は別の仕組みでなんとかする必要があります。</p>



<p>今回は、laravel-modules でモジュール内に作成した middleware を登録する方法をまとめてみます。</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-14" checked><label class="toc-title" for="toc-checkbox-14">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">準備</a></li><li><a href="#toc2" tabindex="0">bootstrap/app.php で登録する</a></li><li><a href="#toc3" tabindex="0">RouteServiceProvider で登録する</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">準備</span></h2>



<p>Laravel と laravel-modules をインストールしておきます。今回は両方とも11です。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>$ composer create-project laravel/laravel .
$ composer require nwidart/laravel-modules</code></pre></div>



<p>忘れがちな設定ですが、extra セクションに merge-plugin を追加します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="composer.json"><code>&quot;extra&quot;: {
    &quot;laravel&quot;: {
        &quot;dont-discover&quot;: []
    },
    &quot;merge-plugin&quot;: {
        &quot;include&quot;: [
            &quot;Modules/*/composer.json&quot;
        ]
    }
},</code></pre></div>



<p>適当な名前のモジュールと middleware を作っておきます。middleware は artisan コマンドを使うと簡単に生成出来ます。</p>




<a rel="noopener" href="https://laravelmodules.com/docs/v11/artisan-commands#module-make-middleware" title="Redirecting to https://laravelmodules.com/docs/1/v11/artisan-commands" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Flaravelmodules.com%2Fdocs%2Fv11%2Fartisan-commands%23module-make-middleware?w=160&#038;h=90" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Redirecting to https://laravelmodules.com/docs/1/v11/artisan-commands</div><div class="blogcard-snippet external-blogcard-snippet"></div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravelmodules.com/docs/v11/artisan-commands#module-make-middleware" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravelmodules.com</div></div></div></div></a>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>$ php artisan module:make Posts
$ php artisan module:make-middleware PostsMiddleware Posts</code></pre></div>



<p>作成した middleware に適当な処理を追加しておきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="PostsMiddleware.php" data-lang="PHP" data-line="15"><code>&lt;?php

namespace Modules\Posts\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class PostsMiddleware
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next)
    {
        echo &#39;Hello, world!! &#39;;
        return $next($request);
    }
}</code></pre></div>



<h2 class="wp-block-heading"><span id="toc2">bootstrap/app.php で登録する</span></h2>



<p>middleware は bootstrap/app.php の <strong>withMiddleware</strong> セクション内で登録するのが正攻法です。</p>




<a rel="noopener" href="https://laravel.com/docs/11.x/middleware#registering-middleware" title="Middleware - Laravel 11.x - The PHP Framework For Web Artisans" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://laravel.com/images/og/laravel-docs.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Middleware - Laravel 11.x - The PHP Framework For Web Artisans</div><div class="blogcard-snippet external-blogcard-snippet">Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravel.com/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravel.com</div></div></div></div></a>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="bootstrap/app.php" data-lang="PHP" data-line="14,15,16,17,18,19,20,21"><code>&lt;?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    -&gt;withRouting(
        web: __DIR__.&#39;/../routes/web.php&#39;,
        commands: __DIR__.&#39;/../routes/console.php&#39;,
        health: &#39;/up&#39;,
    )
    -&gt;withMiddleware(function (Middleware $middleware) {
        // グローバルミドルウェアで登録
        $middleware-&gt;append(\Modules\Posts\Http\Middleware\PostsMiddleware::class);

        // ミドルウェアグループに登録
        $middleware-&gt;appendToGroup(&#39;group&#39;, [\Modules\Posts\Http\Middleware\PostsMiddleware::class]);

        // エイリアスとして登録
        $middleware-&gt;alias([&#39;post&#39; =&gt; \Modules\Posts\Http\Middleware\PostsMiddleware::class ]);
    })
    -&gt;withExceptions(function (Exceptions $exceptions) {
        //
    })-&gt;create();
</code></pre></div>



<p>以下のようなルートを用意して GET リクエストを投げてみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Modules/Posts/routes/web.php" data-lang="PHP"><code>&lt;?php

use Illuminate\Support\Facades\Route;

Route::get(&#39;/posts/global&#39;, function() {
    return &#39;(global)&#39;;
});

Route::get( &#39;/posts/alias&#39;, function() {
    return &#39;(alias)&#39;;
})-&gt;middleware(&#39;post&#39;);

Route::middleware([&#39;group&#39;])-&gt;group(function() {
    Route::get( &#39;/posts/group&#39;, function() {
        return &#39;(group)&#39;;
    });
});</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>$ curl http://localhost:8000/posts/global
Hello, world!! (global)%

$ curl http://localhost:8000/posts/alias 
Hello, world!! Hello, world!! (alias)%

$ curl http://localhost:8000/posts/group
Hello, world!! Hello, world!! (group)%</code></pre></div>



<p>alias と group はグローバルミドルウェアと重複して登録しているので2回出力がありますが、ちゃんと動いていることが確認出来ます。</p>



<p>ただし、グローバルミドルウェアとして登録されているので、モジュール配下のルート以外で定義されたルートでも当該 middleware は適用されてしまいます。この点だけは注意が必要です。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="routes/web.php" data-lang="PHP"><code>&lt;?php

use Illuminate\Support\Facades\Route;

Route::get(&#39;/global&#39;, function() {
    return &#39;(global common)&#39;;
});</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>$ curl http://localhost:8000/global     
Hello, world!! (global common)%</code></pre></div>



<h2 class="wp-block-heading"><span id="toc3">RouteServiceProvider で登録する</span></h2>



<p>別の方法として、RouteServiceProvider に記述することも出来ます。</p>



<p>この RouteServiceProvider は <code>Illuminate\Foundation\Support\Providers\RouteServiceProvider</code> クラスを継承しており、register 内で map メソッドを呼び出します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Foundation/Support/Providers/RouteServiceProvider.php" data-lang="PHP" data-line="14,37,38,39"><code>/**
 * Register any application services.
 *
 * @return void
 */
public function register()
{
    $this-&gt;booted(function () {
        $this-&gt;setRootControllerNamespace();

        if ($this-&gt;routesAreCached()) {
            $this-&gt;loadCachedRoutes();
        } else {
            $this-&gt;loadRoutes();

            $this-&gt;app-&gt;booted(function () {
                $this-&gt;app[&#39;router&#39;]-&gt;getRoutes()-&gt;refreshNameLookups();
                $this-&gt;app[&#39;router&#39;]-&gt;getRoutes()-&gt;refreshActionLookups();
            });
        }
    });
}

/**
 * Load the application routes.
 *
 * @return void
 */
protected function loadRoutes()
{
    if (! is_null(self::$alwaysLoadRoutesUsing)) {
        $this-&gt;app-&gt;call(self::$alwaysLoadRoutesUsing);
    }

    if (! is_null($this-&gt;loadRoutesUsing)) {
        $this-&gt;app-&gt;call($this-&gt;loadRoutesUsing);
    } elseif (method_exists($this, &#39;map&#39;)) {
        $this-&gt;app-&gt;call([$this, &#39;map&#39;]);
    }
}</code></pre></div>



<p>RouteServiceProvider の map メソッド内では、web 及び api ルートに関する定義がされているので、ここで middleware の登録をすると良さそうです。今回はエイリアスを登録して web ルートに適用してみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="RouteServiceProvider" data-lang="PHP" data-line="26,39"><code>&lt;?php

namespace Modules\Posts\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Modules\Posts\Http\Middleware\PostsMiddleware;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * Called before routes are registered.
     *
     * Register any model bindings or pattern based filters.
     */
    public function boot(): void
    {
        parent::boot();
    }

    /**
     * Define the routes for the application.
     */
    public function map(): void
    {
        Route::aliasMiddleware(&#39;post&#39;, PostsMiddleware::class);
        $this-&gt;mapApiRoutes();

        $this-&gt;mapWebRoutes();
    }

    /**
     * Define the &quot;web&quot; routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     */
    protected function mapWebRoutes(): void
    {
        Route::middleware([&#39;web&#39;, &#39;post&#39;])-&gt;group(module_path(&#39;Posts&#39;, &#39;/routes/web.php&#39;));
    }

    /**
     * Define the &quot;api&quot; routes for the application.
     *
     * These routes are typically stateless.
     */
    protected function mapApiRoutes(): void
    {
        Route::middleware(&#39;api&#39;)-&gt;prefix(&#39;api&#39;)-&gt;name(&#39;api.&#39;)-&gt;group(module_path(&#39;Posts&#39;, &#39;/routes/api.php&#39;));
    }
}
</code></pre></div>



<p>GET リクエストを投げてみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>$ curl http://localhost:8000/posts/global
Hello, world!! (global)%</code></pre></div>



<p>middleware が適用されていました。モジュール内で作成した middleware をモジュール内で閉じたい場合はこの方法が良さそうな気がします。</p>



<p>ただし、モジュール配下の RouteServiceProvider 内で登録した middleware のエイリアスはモジュール外でも使えるということと、モジュール外で使った場合、当該モジュールが disabled だとエイリアスが見つからず500エラーを返してしまう点は注意が必要です。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="routes/web.php" data-lang="PHP" data-line="7"><code>&lt;?php

use Illuminate\Support\Facades\Route;

Route::get(&#39;/global&#39;, function() {
    return &#39;(global common)&#39;;
})-&gt;middleware(&#39;post&#39;);</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-json" data-file="modules_statuses.json" data-lang="JSON"><code>{
    &quot;Posts&quot;: false
}</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>$ curl -I  http://localhost:8000/global
HTTP/1.1 500 Internal Server Error</code></pre></div>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】preventAccessingMissingAttributesの挙動+おまけ</title>
		<link>https://wptech.kiichiro.work/87w6kxhjov/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Fri, 31 May 2024 08:27:44 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[コードリーディング]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=2999</guid>

					<description><![CDATA[目次 preventAccessingMissingAttributesの挙動おまけ preventAccessingMissingAttributesの挙動 Laravel の Eloquent を利用してデータを取得 [&#8230;]]]></description>
										<content:encoded><![CDATA[

  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-16" checked><label class="toc-title" for="toc-checkbox-16">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">preventAccessingMissingAttributesの挙動</a></li><li><a href="#toc2" tabindex="0">おまけ</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">preventAccessingMissingAttributesの挙動</span></h2>



<p>Laravel の Eloquent を利用してデータを取得する場合、特に列の指定がなければすべての列情報を取得してきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>$user = User::find(1);

// App\Models\User {
//    id: 5,
//    name: &quot;kiichiro&quot;,
//    email: &quot;kiichiro@example.com&quot;,
//    email_verified_at: &quot;2024-05-28 06:44:34&quot;,
//    password: &quot;$2y$12$jJuJzdMPZ/gSwVSp3DzNE.1SwACJGqPLsWLWP3HGUZRhP1Q/9zJs2&quot;,
//    remember_token: &quot;KjmFVc1rUZ&quot;,
//    created_at: &quot;2024-05-28 06:44:35&quot;,
//    updated_at: &quot;2024-05-28 06:51:32&quot;,
// }</code></pre></div>



<p>列の指定があれば指定された列情報のみを取得します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>$user = User::select([&#39;id&#39;, &#39;name&#39;])-&gt;find(1);

// App\Models\User {
//     id: 1,
//     name: &quot;kiichiro&quot;,
// }
</code></pre></div>



<p>このとき、取得しなかったプロパティへのアクセスが発生すると、null を返します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>$user-&gt;email; // null</code></pre></div>



<p>この挙動故に、アクセスしたプロパティをそもそも取得していないのか、取得した上で null なのかが判断出来ず、バグになってしまうのはよくある話ですね。</p>



<p>これを解決するには <code>AppServiceProvider</code> に <code>Model::preventAccessingMissingAttributes</code> を呼び出すと良いらしいです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="AppServiceProvider.php" data-lang="PHP" data-line="3"><code>public function boot(): void
{
    Model::preventAccessingMissingAttributes();
}</code></pre></div>



<p>取得していないプロパティへのアクセスが発生した場合は <code>MissingAttributeException</code> を投げてくれます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP" data-line="4"><code>$user = User::select([&#39;id&#39;, &#39;name&#39;])-&gt;find(1);
$user-&gt;email;

// Illuminate\Database\Eloquent\MissingAttributeException  The attribute [email] either does not exist or was not retrieved for model [App\Models\User].</code></pre></div>



<p>ただし、列情報を取得していなくても動的プロパティとしてセットしたり、<code>fill</code> でプロパティを埋めた場合はこの例外は投げられません。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>$user = User::select([&#39;id&#39;, &#39;name&#39;])-&gt;find(1);
$user-&gt;email = &#39;kiichiro@example.com&#39;;
$user-&gt;email;
// &#39;kiichiro@example.com&#39;

$user = User::select([&#39;id&#39;, &#39;name&#39;])-&gt;find(1);
$user-&gt;fill([&#39;email&#39; =&gt; &#39;kiichiro@example.com&#39;]);
$user-&gt;email;
// &#39;kiichiro@example.com&#39;</code></pre></div>



<p>プロパティへのアクセスは基本的に <code>attributes</code> という配列のキーに相当するものがあるかどうかを判別しています。判別の結果、<code>attributes</code> になければ<code> <code>MissingAttributeException</code></code> を投げ、<code>attributes</code> にあればその値を返します。よって、動的プロパティとしてセットした場合は <code>attributes</code> にあるので値がそのまま返ってくるということのようです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate\Database\Eloquent\Concerns\HasAttributes.php" data-lang="PHP" data-line="9,27,40,55"><code>/**
 * Dynamically retrieve attributes on the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function __get($key)
{
    return $this-&gt;getAttribute($key);
}

/**
 * Get an attribute from the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function getAttribute($key)
{
    if (! $key) {
        return;
    }

    // If the attribute exists in the attribute array or has a &quot;get&quot; mutator we will
    // get the attribute&#39;s value. Otherwise, we will proceed as if the developers
    // are asking for a relationship&#39;s value. This covers both types of values.
    if ($this-&gt;hasAttribute($key)) {
        return $this-&gt;getAttributeValue($key);
    }

    // Here we will determine if the model base class itself contains this given key
    // since we don&#39;t want to treat any of those methods as relationships because
    // they are all intended as helper methods and none of these are relations.
    if (method_exists(self::class, $key)) {
        return $this-&gt;throwMissingAttributeExceptionIfApplicable($key);
    }

    return $this-&gt;isRelation($key) || $this-&gt;relationLoaded($key)
                ? $this-&gt;getRelationValue($key)
                : $this-&gt;throwMissingAttributeExceptionIfApplicable($key);
}

/**
 * Determine whether an attribute exists on the model.
 *
 * @param  string  $key
 * @return bool
 */
public function hasAttribute($key)
{
    if (! $key) {
        return false;
    }

    return array_key_exists($key, $this-&gt;attributes) ||
        array_key_exists($key, $this-&gt;casts) ||
        $this-&gt;hasGetMutator($key) ||
        $this-&gt;hasAttributeMutator($key) ||
        $this-&gt;isClassCastable($key);
}</code></pre></div>



<h2 class="wp-block-heading"><span id="toc2">おまけ</span></h2>



<p><code>preventAccessingMissingAttributes</code> は Laravel 9系で実装された機能なので、Laravel 9 の公式ドキュメントには記載があるのですが、なぜか10以降のドキュメントから削除されていました。</p>




<a rel="noopener" href="https://laravel.com/docs/9.x/eloquent#configuring-eloquent-strictness" title="Eloquent: Getting Started - Laravel 9.x - The PHP Framework For Web Artisans" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://laravel.com/images/og/laravel-docs.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Eloquent: Getting Started - Laravel 9.x - The PHP Framework For Web Artisans</div><div class="blogcard-snippet external-blogcard-snippet">Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravel.com/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravel.com</div></div></div></div></a>




<a rel="noopener" href="https://laravel.com/docs/10.x/eloquent#configuring-eloquent-strictness" title="Eloquent: Getting Started - Laravel 10.x - The PHP Framework For Web Artisans" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://laravel.com/images/og/laravel-docs.png" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">Eloquent: Getting Started - Laravel 10.x - The PHP Framework For Web Artisans</div><div class="blogcard-snippet external-blogcard-snippet">Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://laravel.com/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">laravel.com</div></div></div></div></a>



<p>どうやら以下のコミットで削除されたようです。</p>




<a rel="noopener" href="https://github.com/laravel/docs/commit/616edf4f7dcc58e069a304ba49c654551bdcf460" title="wip · laravel/docs@616edf4" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://opengraph.githubassets.com/7b39e0b7da077a007cfe5b2685ada2acb20177921ab68d9f3b7a87c35ec8c036/laravel/docs/commit/616edf4f7dcc58e069a304ba49c654551bdcf460" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">wip · laravel/docs@616edf4</div><div class="blogcard-snippet external-blogcard-snippet">The Laravel documentation. Contribute to laravel/docs development by creating an account on GitHub.</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://github.com/laravel/docs/commit/616edf4f7dcc58e069a304ba49c654551bdcf460" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">github.com</div></div></div></div></a>



<p>Eloquent ORM の機能を制限するガードレールとして設定したいという需要がありそうなので、ドキュメントの削除はミスかなと思ったのですが、同じようなことを思った人が既にいたようでプルリクエストが上がっていました。</p>




<a rel="noopener" href="https://github.com/laravel/docs/pull/9260" title="[10.x] Adding Missing Content of `Configuring Eloquent Strictness` by devajmeireles · Pull Request #9260 · laravel/docs" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://opengraph.githubassets.com/3cd3872fcdb93b0fb1368e07f79ef153ceb5f4958e48b2c2d46f2f4156566e30/laravel/docs/pull/9260" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">[10.x] Adding Missing Content of `Configuring Eloquent Strictness` by devajmeireles · Pull Request #9260 · laravel/docs</div><div class="blogcard-snippet external-blogcard-snippet">I used 9.x as a base to include the missing content in 10.x</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://github.com/laravel/docs/pull/9260" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">github.com</div></div></div></div></a>



<p>コメントに削除した理由についての言及がありました。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>I don&#8217;t really like this feature so I removed it from the docs.</p>
<cite><a href="https://github.com/laravel/docs/pull/9260#issuecomment-1892644460">https://github.com/laravel/docs/pull/9260#issuecomment-1892644460</a></cite></blockquote>



<p>まあ、好みじゃないならしょうがないですよね。。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】JSON型のカラムにindexを生成してもindexが効かなくなった</title>
		<link>https://wptech.kiichiro.work/95lrw1tolj/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Thu, 15 Jun 2023 15:19:47 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=1708</guid>

					<description><![CDATA[本稿では、JSON型のカラムにindexを貼ってみたものの、Laravelで想定通りindexを使ってくれなかったケースを紹介します。 目次 JSON型カラムJSON型のカラムにindex貼ってみたLaravelでやって [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="">本稿では、JSON型のカラムにindexを貼ってみたものの、Laravelで想定通りindexを使ってくれなかったケースを紹介します。</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-18" checked><label class="toc-title" for="toc-checkbox-18">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">JSON型カラム</a></li><li><a href="#toc2" tabindex="0">JSON型のカラムにindex貼ってみた</a></li><li><a href="#toc3" tabindex="0">Laravelでやってみた</a></li><li><a href="#toc4" tabindex="0">indexが使われない原因</a></li><li><a href="#toc5" tabindex="0">indexが使われるようにする</a></li><li><a href="#toc6" tabindex="0">おまけ</a></li><li><a href="#toc7" tabindex="0">参考リンク</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">JSON型カラム</span></h2>



<p class="">MySQLのJSON型って便利ですよね。あらかじめデータ構造を定義する必要が無いので、データ構造がまだ決まっていなかったり、頻繁に変更があったりする場合は、とりあえずJSON型にして突っ込んでおくという使い方ができますし。</p>



<p class="">ただ、なんでもかんでもJSON型のカラムに突っ込んで痛い目を見てきた身からすると、使うケースは熟考した方が良いだろうなとも思います。特にJSON型のカラムのindex周りは注意が必要です。</p>



<h2 class="wp-block-heading"><span id="toc2">JSON型のカラムにindex貼ってみた</span></h2>



<p class="">MySQLでJSON型のカラムにindexを付けるには、Generated Columnを使うことが多いようです。Laravelのmigrationでは、<code>virtualAs</code> を使ってGenerated Columnを利用することができます。</p>



<p class="">以下は <code>post_meta</code> カラムはJSON型で、<code>slug</code> という属性に対してindexを貼りたいときのmigrationの例です。<code>post_meta_slug</code> をGenerated Columnで追加し、そこにindexを追加しています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="migration.php" data-lang="PHP"><code>public function up(): void
{
    Schema::create(&#39;posts&#39;, function (Blueprint $table) {
        $table-&gt;id();
        $table-&gt;json(&#39;post_meta&#39;);
        $table-&gt;string(&#39;post_meta_slug&#39;)-&gt;nullable()-&gt;virtualAs(&#39;JSON_UNQUOTE(post_meta-&gt;&quot;$.slug&quot;)&#39;);
        $table-&gt;index(&#39;post_meta_slug&#39;);
    });
}</code></pre></div>



<p class="">CREATE TABLEを見てみると、Generated Columnが生成され、indexが追加されていることが確認できます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>mysql&gt; show create table posts;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                                                                                                                                                                            |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| posts | CREATE TABLE `posts` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `post_meta` json NOT NULL,
  `post_meta_slug` varchar(255) COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS (json_unquote(json_extract(`post_meta`,_utf8mb4&#39;$.slug&#39;))) VIRTUAL,
  PRIMARY KEY (`id`),
  KEY `posts_post_meta_slug_index` (`post_meta_slug`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)</code></pre></div>



<p class="">実際にテストデータを入れてindexが効いているかどうかの確認のため、実行計画を見てみます。<br>まずはデータを用意します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-sql" data-lang="SQL"><code>INSERT INTO posts (post_meta) values
(&#39;{&quot;slug&quot;:&quot;6nwolmsp8y&quot;, &quot;title&quot;: &quot;AWS-CDKWordPress&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;4yeiux7wqq&quot;, &quot;title&quot;: &quot;WPScanxmlrpc&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;3yjela8tgv&quot;, &quot;title&quot;: &quot;WordPressHTTPHTTPS (version5.7)&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;4771lv37ot&quot;, &quot;title&quot;: &quot;Laravel SailMySQLPostgres&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;2xch5zq3e9&quot;, &quot;title&quot;: &quot;BeautifulSoupSelenium&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;1xbq0pridw&quot;, &quot;title&quot;: &quot;ACF10Delicious Brains Inc.&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;10bwr53umq&quot;, &quot;title&quot;: &quot;Amazon LightsailAWS CDK&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;43nyielz64&quot;, &quot;title&quot;: &quot;Amazon LightsailSSL&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;40a2u2w7rv&quot;, &quot;title&quot;: &quot;AWS Solutions Architect Associate&quot;}&#39;),
(&#39;{&quot;slug&quot;:&quot;26ru99uao0&quot;, &quot;title&quot;: &quot;AWS CDKTypeScriptLambda + SAM Local&quot;}&#39;)
;</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>mysql&gt; select * from posts;
+----+------------------------------------------------------------------------+----------------+
| id | post_meta                                                              | post_meta_slug |
+----+------------------------------------------------------------------------+----------------+
|  1 | {&quot;slug&quot;: &quot;6nwolmsp8y&quot;, &quot;title&quot;: &quot;AWS-CDKWordPress&quot;}                    | 6nwolmsp8y     |
|  2 | {&quot;slug&quot;: &quot;4yeiux7wqq&quot;, &quot;title&quot;: &quot;WPScanxmlrpc&quot;}                        | 4yeiux7wqq     |
|  3 | {&quot;slug&quot;: &quot;3yjela8tgv&quot;, &quot;title&quot;: &quot;WordPressHTTPHTTPS (version5.7)&quot;}     | 3yjela8tgv     |
|  4 | {&quot;slug&quot;: &quot;4771lv37ot&quot;, &quot;title&quot;: &quot;Laravel SailMySQLPostgres&quot;}           | 4771lv37ot     |
|  5 | {&quot;slug&quot;: &quot;2xch5zq3e9&quot;, &quot;title&quot;: &quot;BeautifulSoupSelenium&quot;}               | 2xch5zq3e9     |
|  6 | {&quot;slug&quot;: &quot;1xbq0pridw&quot;, &quot;title&quot;: &quot;ACF10Delicious Brains Inc.&quot;}          | 1xbq0pridw     |
|  7 | {&quot;slug&quot;: &quot;10bwr53umq&quot;, &quot;title&quot;: &quot;Amazon LightsailAWS CDK&quot;}             | 10bwr53umq     |
|  8 | {&quot;slug&quot;: &quot;43nyielz64&quot;, &quot;title&quot;: &quot;Amazon LightsailSSL&quot;}                 | 43nyielz64     |
|  9 | {&quot;slug&quot;: &quot;40a2u2w7rv&quot;, &quot;title&quot;: &quot;AWS Solutions Architect Associate&quot;}   | 40a2u2w7rv     |
| 10 | {&quot;slug&quot;: &quot;26ru99uao0&quot;, &quot;title&quot;: &quot;AWS CDKTypeScriptLambda + SAM Local&quot;} | 26ru99uao0     |
+----+------------------------------------------------------------------------+----------------+
10 rows in set (0.00 sec)</code></pre></div>



<p class=""><code>post_meta_slug</code> にダブルクオーテーションが除去された文字列が入っていることが確認できます。</p>



<p class="">JSON型のカラムの属性でWHERE句で絞り込みたい場合は、JSON_EXTRACTまたは短縮演算子-&gt;を使用します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>mysql&gt; SELECT * FROM posts WHERE `post_meta`-&gt;&quot;$.slug&quot; = &quot;6nwolmsp8y&quot;;
+----+-----------------------------------------------------+----------------+
| id | post_meta                                           | post_meta_slug |
+----+-----------------------------------------------------+----------------+
|  1 | {&quot;slug&quot;: &quot;6nwolmsp8y&quot;, &quot;title&quot;: &quot;AWS-CDKWordPress&quot;} | 6nwolmsp8y     |
+----+-----------------------------------------------------+----------------+
1 row in set (0.02 sec)

mysql&gt; SELECT * FROM posts WHERE JSON_UNQUOTE(JSON_EXTRACT(`post_meta`, &quot;$.slug&quot;))  = &quot;6nwolmsp8y&quot;;
+----+-----------------------------------------------------+----------------+
| id | post_meta                                           | post_meta_slug |
+----+-----------------------------------------------------+----------------+
|  1 | {&quot;slug&quot;: &quot;6nwolmsp8y&quot;, &quot;title&quot;: &quot;AWS-CDKWordPress&quot;} | 6nwolmsp8y     |
+----+-----------------------------------------------------+----------------+
1 row in set (0.00 sec)
</code></pre></div>



<p class="">実行計画を出してみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>mysql&gt; EXPLAIN SELECT * FROM posts WHERE `post_meta`-&gt;&quot;$.slug&quot; = &quot;6nwolmsp8y&quot;;
+----+-------------+-------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys              | key                        | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | posts | NULL       | ref  | posts_post_meta_slug_index | posts_post_meta_slug_index | 1023    | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql&gt; EXPLAIN SELECT * FROM posts WHERE JSON_UNQUOTE(JSON_EXTRACT(`post_meta`, &quot;$.slug&quot;))  = &quot;6nwolmsp8y&quot;;
+----+-------------+-------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys              | key                        | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | posts | NULL       | ref  | posts_post_meta_slug_index | posts_post_meta_slug_index | 1023    | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)</code></pre></div>



<p class="">keyを見ると、Generated Columnで追加したカラムを含むindexが使用されていることが確認できます。</p>



<h2 class="wp-block-heading"><span id="toc3">Laravelでやってみた</span></h2>



<p class="">Laravel側の準備をします。postsテーブルに対応するPostモデルを作っておきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Post.php" data-lang="PHP"><code>&lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model { }</code></pre></div>



<p class="">tinkerを起動してEloquentを使ってクエリを組み立てます。今回は実行計画を見たいので、explain()を使います。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>&gt; \App\Models\Post::where(&#39;post_meta-&gt;slug&#39;, &#39;6nwolmsp8y&#39;)-&gt;explain();
= Illuminate\Support\Collection {#6207
    all: [
      {#7161
        +&quot;id&quot;: 1,
        +&quot;select_type&quot;: &quot;SIMPLE&quot;,
        +&quot;table&quot;: &quot;posts&quot;,
        +&quot;partitions&quot;: null,
        +&quot;type&quot;: &quot;ALL&quot;,
        +&quot;possible_keys&quot;: null,
        +&quot;key&quot;: null,
        +&quot;key_len&quot;: null,
        +&quot;ref&quot;: null,
        +&quot;rows&quot;: 10,
        +&quot;filtered&quot;: 100.0,
        +&quot;Extra&quot;: &quot;Using where&quot;,
      },
    ],
  }</code></pre></div>



<p class="">indexは使われていないようです。。</p>



<h2 class="wp-block-heading"><span id="toc4">indexが使われない原因</span></h2>



<p class="">toSqlで実際にどのようなクエリが組み立てられているのかを確認してみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>&gt; \App\Models\Post::where(&#39;post_meta-&gt;slug&#39;, &#39;6nwolmsp8y&#39;)-&gt;toSql();
= &quot;select * from `posts` where json_unquote(json_extract(`post_meta`, &#39;$.&quot;slug&quot;&#39;)) = ?&quot;</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-sql" data-lang="SQL"><code>SELECT * FROM `posts` WHERE JSON_UNQUOTE(JSON_EXTRACT(`post_meta`, &#39;$.&quot;slug&quot;&#39;)) = &quot;6nwolmsp8y&quot;;</code></pre></div>



<p class="">一見、何も問題ないように見えますが、slugがダブルクオーテーションで括られていることがわかります。</p>



<p class="">Laravelではwhereで「->」を含む文字列が渡された場合、JSON型へのクエリーであると判断してJSONの組み込み関数へと展開されます。同時にJSONのパスをダブルクオーテーションで括るように変換するようです。</p>



<p class="">公式ドキュメントには以下の記述があります。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="">クエリー式が生成されたカラム定義と一致するには、式が同一であり、同じ結果タイプである必要があります。 たとえば、生成されたカラム式が&nbsp;<code>f1 + 1</code>&nbsp;の場合、クエリーで&nbsp;<code>1 + f1</code>&nbsp;が使用されているか、<code>f1 + 1</code>&nbsp;(整数式) が文字列と比較されても、オプティマイザは一致を認識しません。</p>
<cite><a href="https://dev.mysql.com/doc/refman/8.0/ja/generated-column-index-optimizations.html">https://dev.mysql.com/doc/refman/8.0/ja/generated-column-index-optimizations.html</a></cite></blockquote>



<p class="">おそらく、オプティマイザがGenerated Columnの式とクエリー式が一致していないと判断しているため、indexが使われないと考えられます。</p>



<h2 class="wp-block-heading"><span id="toc5">indexが使われるようにする</span></h2>



<p class="">1つは、<code>whereRaw</code> を使う方法が挙げられます。<code>whereRaw</code>は素のwhere句を記述することができるので、勝手に変換されることを回避できます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>&gt; \App\Models\Post::query()-&gt;whereRaw(&#39;JSON_UNQUOTE(JSON_EXTRACT(post_meta, &quot;$.slug&quot;)) = ?&#39;, [&#39;4yeiux7wqq&#39;])-&gt;explain();
= Illuminate\Support\Collection {#7174
    all: [
      {#6210
        +&quot;id&quot;: 1,
        +&quot;select_type&quot;: &quot;SIMPLE&quot;,
        +&quot;table&quot;: &quot;posts&quot;,
        +&quot;partitions&quot;: null,
        +&quot;type&quot;: &quot;ref&quot;,
        +&quot;possible_keys&quot;: &quot;posts_post_meta_slug_index&quot;,
        +&quot;key&quot;: &quot;posts_post_meta_slug_index&quot;,
        +&quot;key_len&quot;: &quot;1023&quot;,
        +&quot;ref&quot;: &quot;const&quot;,
        +&quot;rows&quot;: 1,
        +&quot;filtered&quot;: 100.0,
        +&quot;Extra&quot;: null,
      },
    ],
  }</code></pre></div>



<p class="">もう一つは、Generated Columnをwhere句に指定する方法です。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>&gt; \App\Models\Post::query()-&gt;where(&#39;post_meta_slug&#39;, &#39;4yeiux7wqq&#39;)-&gt;explain();
= Illuminate\Support\Collection {#7175
    all: [
      {#7165
        +&quot;id&quot;: 1,
        +&quot;select_type&quot;: &quot;SIMPLE&quot;,
        +&quot;table&quot;: &quot;posts&quot;,
        +&quot;partitions&quot;: null,
        +&quot;type&quot;: &quot;ref&quot;,
        +&quot;possible_keys&quot;: &quot;posts_post_meta_slug_index&quot;,
        +&quot;key&quot;: &quot;posts_post_meta_slug_index&quot;,
        +&quot;key_len&quot;: &quot;1023&quot;,
        +&quot;ref&quot;: &quot;const&quot;,
        +&quot;rows&quot;: 1,
        +&quot;filtered&quot;: 100.0,
        +&quot;Extra&quot;: null,
      },
    ],
  }</code></pre></div>



<p class="">どちらもindexを使っていることを確認できますが、個人的には後者の方がシンプルで良いと思います。</p>



<h2 class="wp-block-heading"><span id="toc6">おまけ</span></h2>



<p class="">MySQL 8.0.13以降では、関数インデックスが使用できます。やっていることはGenerated Columnとあまり変わりないですが、CHARでCASTすることとcollationを明示する必要があるところが異なります。</p>



<p class="">LaravelではrawIndexでもcollationの指定ができなかった(多分)ので、DB::statementで直接ALTER TABLEを書いています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="migration.php" data-lang="PHP"><code>public function up(): void
{
    Schema::create(&#39;posts&#39;, function (Blueprint $table) {
        $table-&gt;id();
        $table-&gt;json(&#39;post_meta&#39;);
    });
    DB::statement(&#39;ALTER TABLE posts ADD INDEX post_meta_slug_index((CAST(post_meta-&gt;&gt;&quot;$.slug&quot; as CHAR(255)) COLLATE utf8mb4_bin))&#39;);
}</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>mysql&gt; show create table posts;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                                                                                                    |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| posts | CREATE TABLE `posts` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `post_meta` json NOT NULL,
  PRIMARY KEY (`id`),
  KEY `post_meta_slug_index` (((cast(json_unquote(json_extract(`post_meta`,_utf8mb4&#39;$.slug&#39;)) as char(255) charset utf8mb4) collate utf8mb4_bin)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)</code></pre></div>



<p class="">関数インデックスでも式が一致していないとindexが使われないというのは同じようです。</p>



<h2 class="wp-block-heading"><span id="toc7">参考リンク</span></h2>



<ul class="wp-block-list">
<li><a href="https://dev.mysql.com/doc/refman/8.0/ja/json.html">https://dev.mysql.com/doc/refman/8.0/ja/json.html</a></li>



<li><a href="https://dev.mysql.com/doc/refman/8.0/ja/create-table-generated-columns.html">https://dev.mysql.com/doc/refman/8.0/ja/create-table-generated-columns.html</a></li>



<li><a href="https://dev.mysql.com/doc/refman/8.0/ja/generated-column-index-optimizations.html">https://dev.mysql.com/doc/refman/8.0/ja/generated-column-index-optimizations.html</a></li>



<li><a href="https://dev.mysql.com/doc/refman/8.0/ja/create-index.html#create-index-functional-key-parts">https://dev.mysql.com/doc/refman/8.0/ja/create-index.html#create-index-functional-key-parts</a></li>



<li><a href="https://dev.mysql.com/blog-archive/indexing-json-documents-via-virtual-columns/">https://dev.mysql.com/blog-archive/indexing-json-documents-via-virtual-columns/</a></li>



<li><a href="https://blogs.oracle.com/mysql-jp/post/indexing-json-data-in-mysql-jp">https://blogs.oracle.com/mysql-jp/post/indexing-json-data-in-mysql-jp</a></li>



<li><a href="https://planetscale.com/blog/indexing-json-in-mysql">https://planetscale.com/blog/indexing-json-in-mysql</a></li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PhpStormで「Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?」が出たときの対処法</title>
		<link>https://wptech.kiichiro.work/37c8w1j5zy/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Tue, 30 May 2023 08:39:18 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://wptech.kiichiro.work/?p=1634</guid>

					<description><![CDATA[Docker Desktop for Macを使っている場合の話。 目次 問題結論と解決策参考リンク 問題 PhpStormでDocker用にインタプリターを構成してPHPUnitを実行したところ、以下のメッセージが表示 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Docker Desktop for Macを使っている場合の話。</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-20" checked><label class="toc-title" for="toc-checkbox-20">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">問題</a></li><li><a href="#toc2" tabindex="0">結論と解決策</a></li><li><a href="#toc3" tabindex="0">参考リンク</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">問題</span></h2>



<p>PhpStormでDocker用にインタプリターを構成してPHPUnitを実行したところ、以下のメッセージが表示されました。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="142" src="https://wptech.kiichiro.work/wp-content/uploads/2023/05/18f11e571a133aee218caef83066b334-1024x142.png" alt="Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? が発生し、phpunitが失敗する" class="wp-image-1637" srcset="https://wptech.kiichiro.work/wp-content/uploads/2023/05/18f11e571a133aee218caef83066b334-1024x142.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/18f11e571a133aee218caef83066b334-300x42.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/18f11e571a133aee218caef83066b334-768x107.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/18f11e571a133aee218caef83066b334-1536x213.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/18f11e571a133aee218caef83066b334.png 1614w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?</code></pre></div>



<p>PhpStorm側の設定は何も変えておらず、解決に少し時間がかかったので備忘録として残しておきます。</p>



<h2 class="wp-block-heading"><span id="toc2">結論と解決策</span></h2>



<p>「Enable default Docker socket」 にチェックを入れてDocker Desktopを再起動してください。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="402" src="https://wptech.kiichiro.work/wp-content/uploads/2023/05/3f5841b30d8bf84bc824b128ad6a5ea0-1024x402.png" alt="Docker Desktop Advanced settings" class="wp-image-1641" srcset="https://wptech.kiichiro.work/wp-content/uploads/2023/05/3f5841b30d8bf84bc824b128ad6a5ea0-1024x402.png 1024w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/3f5841b30d8bf84bc824b128ad6a5ea0-300x118.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/3f5841b30d8bf84bc824b128ad6a5ea0-768x301.png 768w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/3f5841b30d8bf84bc824b128ad6a5ea0-1536x602.png 1536w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/3f5841b30d8bf84bc824b128ad6a5ea0-2048x803.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>PhpStormなどのツールとDockerを統合するには、Docker socketへのアクセスが必要となるそうです。Docker Desktop 4.18以降では、これの有効/無効を拡張設定から行うことができます。</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" src="https://wptech.kiichiro.work/wp-content/uploads/2023/05/d00a043a755fb073264ea11a51e1890b.png" alt="phpunit成功" class="wp-image-1643" width="737" height="344" srcset="https://wptech.kiichiro.work/wp-content/uploads/2023/05/d00a043a755fb073264ea11a51e1890b.png 982w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/d00a043a755fb073264ea11a51e1890b-300x140.png 300w, https://wptech.kiichiro.work/wp-content/uploads/2023/05/d00a043a755fb073264ea11a51e1890b-768x358.png 768w" sizes="(max-width: 737px) 100vw, 737px" /></figure>



<p>設定を変更してPHPUnitを実行すると正常に終了しました。</p>



<h2 class="wp-block-heading"><span id="toc3">参考リンク</span></h2>



<ul class="wp-block-list">
<li><a href="https://docs.docker.com/desktop/mac/permission-requirements/">https://docs.docker.com/desktop/mac/permission-requirements/</a></li>



<li><a href="https://www.docker.com/blog/docker-desktop-4-18/">https://www.docker.com/blog/docker-desktop-4-18/</a></li>



<li><a href="https://www.techtarget.com/searchitoperations/tip/Get-informed-of-the-risks-associated-with-dockersock#:~:text=A%20socket%20is%20an%20endpoint,sock%20file.">https://www.techtarget.com/searchitoperations/tip/Get-informed-of-the-risks-associated-with-dockersock#:~:text=A%20socket%20is%20an%20endpoint,sock%20file.</a></li>
</ul>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
