<?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>Laravel | きいちログ</title>
	<atom:link href="https://wptech.kiichiro.work/tag/laravel/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>【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-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">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>【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-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">準備</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-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">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-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">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>【Laravel】MacroableでPHPのオーバーロードを学ぶ</title>
		<link>https://wptech.kiichiro.work/5804m1k8bh/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Sat, 11 Mar 2023 15:49:20 +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=1442</guid>

					<description><![CDATA[PHPのオーバーロードを説明する上でLaravelのMacroableがちょうど良いと思ったので。 マクロについて Laravel内部の一部のクラスには、独自に定義したメソッドを追加できる「マクロ」という機能が備わってい [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>PHPのオーバーロードを説明する上でLaravelのMacroableがちょうど良いと思ったので。</p>



<h1 class="wp-block-heading">マクロについて</h1>



<p>Laravel内部の一部のクラスには、独自に定義したメソッドを追加できる「マクロ」という機能が備わっています。</p>



<p>マクロを追加したいクラスにMacroableをuse宣言することによって、当該クラスにマクロを登録することが出来ます。<br>例えば <code>Illuminate\Support\Collection</code> はMacroableをuseしているので、以下のようにしてマクロの登録が行えます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="AppServiceProvider.php" data-lang="PHP"><code>public function boot()
{
    Collection::macro(&#39;foo&#39;, function() {
        $this-&gt;map(function($value) {
            // 処理
        });
    });
}</code></pre></div>



<p>よくあるマクロの使いどころとしては、<code>Collection</code> や <code>Response</code> といったFacadesに対して、汎用的に再利用したい処理がある場合などかと思っています。</p>



<p>このマクロはPHPのオーバーロードを利用して実現されています。</p>



<h1 class="wp-block-heading">オーバーロードについて</h1>



<p>PHPにおけるオーバーロードとは、プロパティやメソッドを動的に作成する手法であり、マジックメソッドを利用します。また、オーバーロードメソッドは宣言されていないプロパティやメソッドが呼び出された際に起動します。</p>




<a rel="noopener" href="https://www.php.net/manual/ja/language.oop5.overloading.php" title="PHP: &#12458;&#12540;&#12496;&#12540;&#12525;&#12540;&#12489; - Manual" 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://wptech.kiichiro.work/wp-content/uploads/cocoon-resources/blog-card-cache/ae2f27571af4ab50ab3f001c87ae24dd.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">PHP: &#12458;&#12540;&#12496;&#12540;&#12525;&#12540;&#12489; - Manual</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://www.php.net/manual/ja/language.oop5.overloading.php" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.php.net</div></div></div></div></a>



<p>未宣言のメソッドに関しては <code>__call</code> というマジックメソッドを用意しておくことで起動出来ます (staticは <code>__callStatic</code> )。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>// __callを宣言していないクラス
class Bar {}

$bar = new Bar();
$bar-&gt;bar(); // PHP Fatal error:  Uncaught Error: Call to undefined method Bar::bar()

// __callを宣言したクラス
class Foo {
    public function __call($method, $parameters) {
        echo &#39;Method: &#39; . $method;
    }
}

$foo = new Foo();
$foo-&gt;foo(); // Method: foo</code></pre></div>



<p>また、同名で引数の型や数の異なるメソッドを宣言する(一般的な)オーバーロードとは解釈が異なります。</p>



<h1 class="wp-block-heading">コードリーディング</h1>



<p>実際に以下の流れでコードを見ていきます。</p>



<ol class="wp-block-list">
<li>マクロを登録する</li>



<li>マクロを呼び出す</li>
</ol>




  <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><li><a href="#toc1" tabindex="0">マクロを登録する</a></li><li><a href="#toc2" tabindex="0">マクロを呼び出す</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">マクロを登録する</span></h2>



<p>まずはマクロの登録時の処理を見ていきます。登録時の処理はMacroableに定義されています。</p>



<p>登録時に呼び出すmacroメソッドですが、第一引数の呼び出す際のメソッド名をkeyに、第二引数のクロージャを valueとして <code>$macro</code> というプロパティに詰め込んでいます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate\Support\Traits\Macroable.php" data-lang="PHP"><code>/**
 * The registered string macros.
 *
 * @var array
 */
protected static $macros = [];

/**
 * Register a custom macro.
 *
 * @param  string  $name
 * @param  object|callable  $macro
 * @return void
 */
public static function macro($name, $macro)
{
    static::$macros[$name] = $macro;
}</code></pre></div>



<p>冒頭で例に上げたCollectionクラスは、Macroableをuseしていますので、 <code>Collection::macro</code> を呼び出すことで、<code>$macro</code> にメソッド名 <code>foo</code> とクロージャが登録されることになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="AppServiceProvider.php" data-lang="PHP"><code>public function boot()
{
    Collection::macro(&#39;foo&#39;, function() {
        $this-&gt;map(function($value) {
            // 処理
        });
    });
}</code></pre></div>



<h2 class="wp-block-heading"><span id="toc2">マクロを呼び出す</span></h2>



<p>登録したマクロはメソッド名を <code>-></code> または <code>::</code> で呼び出すことで利用が出来ます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>// インスタンス化して呼び出す
collect()-&gt;foo();

// staticメソッドとして呼び出す
Collection::foo();</code></pre></div>



<p>このとき、Collectionにfooメソッドは宣言されていないのでオーバーロードされ、該当するマジックメソッドが呼び出されます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate\Support\Traits\Macroable.php" data-lang="PHP"><code>/**
 * Dynamically handle calls to the class.
 *
 * @param  string  $method
 * @param  array  $parameters
 * @return mixed
 *
 * @throws \BadMethodCallException
 */
public static function __callStatic($method, $parameters)
{
    if (! static::hasMacro($method)) {
        throw new BadMethodCallException(sprintf(
            &#39;Method %s::%s does not exist.&#39;, static::class, $method
        ));
    }

    $macro = static::$macros[$method];

    if ($macro instanceof Closure) {
        $macro = $macro-&gt;bindTo(null, static::class);
    }

    return $macro(...$parameters);
}

/**
 * Dynamically handle calls to the class.
 *
 * @param  string  $method
 * @param  array  $parameters
 * @return mixed
 *
 * @throws \BadMethodCallException
 */
public function __call($method, $parameters)
{
    if (! static::hasMacro($method)) {
        throw new BadMethodCallException(sprintf(
            &#39;Method %s::%s does not exist.&#39;, static::class, $method
        ));
    }

    $macro = static::$macros[$method];

    if ($macro instanceof Closure) {
        $macro = $macro-&gt;bindTo($this, static::class);
    }

    return $macro(...$parameters);
}</code></pre></div>



<p><code>__call</code> でも <code>__callStatic</code> でも概ねやっていることは同じで、</p>



<ol class="wp-block-list">
<li><code>hasMacro</code> で呼び出されたメソッド名がマクロとして登録されているか(<code>$macro</code>に該当するメソッド名が存在するか)を検証する
<ul class="wp-block-list">
<li>存在しなければBadMethodCallExceptionをthrowする</li>
</ul>
</li>



<li>クロージャをバインドする
<ul class="wp-block-list">
<li><code>__call</code> : 呼び出されたメソッドを持つオブジェクトにクロージャをバインドする</li>



<li><code>__callStatic</code> : バインドを解除する</li>
</ul>
</li>



<li>当該メソッドを引き渡されたパラメータで実行する</li>
</ol>



<p>という流れになります。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】EloquentのsaveではCarbonオブジェクトをStringに変換している</title>
		<link>https://wptech.kiichiro.work/59bqu2qa9d/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Tue, 27 Dec 2022 09:12:16 +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=1242</guid>

					<description><![CDATA[目次 はじめにコードリーディングfillsaveおわりに はじめに Eloquentを利用すると、以下の①、②のどちらのパターンでも保存することが出来ます。 ②では、datetime型のカラムに文字列ではなくCarbon [&#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">はじめに</a></li><li><a href="#toc2" tabindex="0">コードリーディング</a><ol><li><a href="#toc3" tabindex="0">fill</a></li><li><a href="#toc4" tabindex="0">save</a></li></ol></li><li><a href="#toc5" tabindex="0">おわりに</a></li></ol>
    </div>
  </div>

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



<p>Eloquentを利用すると、以下の①、②のどちらのパターンでも保存することが出来ます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-lang="PHP"><code>$foo = Foo::getModel();

// ①明示的にStringを渡す
$foo-&gt;fill([
    &#39;datetime&#39; =&gt; Carbon::now()-&gt;toDateTimeString(),
    &#39;date&#39;     =&gt; Carbon::now()-&gt;toDateString(),
])-&gt;save();

// ②Carbonオブジェクトのまま渡す
$foo-&gt;fill([
    &#39;datetime&#39; =&gt; Carbon::now(),
    &#39;date&#39;     =&gt; Carbon::now(),
])-&gt;save();</code></pre></div>



<p>②では、datetime型のカラムに文字列ではなくCarbonオブジェクトを渡していますが、おそらくSQLを組み立てる際にキャストして文字列を組み立てているのだろうな、とは思っていました。</p>



<p>ただ、どちらでもOKというのが腑に落ちなかったので、該当箇所のコードリーディングをしてみます。</p>



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



<h3 class="wp-block-heading"><span id="toc3">fill</span></h3>



<p>fillでは主に、「入力されたattributesがassignableであれば <code>$attributes</code> 配列(property)に詰め込む」という処理を行っています。</p>




<a rel="noopener" href="https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php#L499-L547" title="framework/src/Illuminate/Database/Eloquent/Model.php at 9.x · laravel/framework" 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%2Fgithub.com%2Flaravel%2Fframework%2Fblob%2F9.x%2Fsrc%2FIlluminate%2FDatabase%2FEloquent%2FModel.php%23L499-L547?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">framework/src/Illuminate/Database/Eloquent/Model.php at 9.x · laravel/framework</div><div class="blogcard-snippet external-blogcard-snippet">The Laravel Framework. Contribute to laravel/framework 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/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php#L499-L547" 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>実際に詰め込んでいるのは、 <code>HasAttributes</code> traitです。</p>




<a rel="noopener" href="https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L938-L993" title="framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php at 9.x · laravel/framework" 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%2Fgithub.com%2Flaravel%2Fframework%2Fblob%2F9.x%2Fsrc%2FIlluminate%2FDatabase%2FEloquent%2FConcerns%2FHasAttributes.php%23L938-L993?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">framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php at 9.x · laravel/framework</div><div class="blogcard-snippet external-blogcard-snippet">The Laravel Framework. Contribute to laravel/framework 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/framework/blob/9.x/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L938-L993" 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>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="HasAttributes.php" data-lang="PHP"><code>$this-&gt;attributes[$key] = $value;</code></pre></div>



<h3 class="wp-block-heading"><span id="toc4">save</span></h3>



<p>saveでは、fillで詰め込んだattributesを元にSQLを組み立て実行します。</p>




<a rel="noopener" href="https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php#L1097-L1144" title="framework/src/Illuminate/Database/Eloquent/Model.php at 9.x · laravel/framework" 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%2Fgithub.com%2Flaravel%2Fframework%2Fblob%2F9.x%2Fsrc%2FIlluminate%2FDatabase%2FEloquent%2FModel.php%23L1097-L1144?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">framework/src/Illuminate/Database/Eloquent/Model.php at 9.x · laravel/framework</div><div class="blogcard-snippet external-blogcard-snippet">The Laravel Framework. Contribute to laravel/framework 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/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php#L1097-L1144" 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>途中は省略しますが、insert実行までの一連の処理を追っていくと、Connectionクラス の <code>prepareBindings</code> に到達します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Connection.php" data-lang="PHP"><code>    /**
     * Prepare the query bindings for execution.
     *
     * @param  array  $bindings
     * @return array
     */
    public function prepareBindings(array $bindings)
    {
        $grammar = $this-&gt;getQueryGrammar();

        foreach ($bindings as $key =&gt; $value) {
            // We need to transform all instances of DateTimeInterface into the actual
            // date string. Each query grammar maintains its own date string format
            // so we&#39;ll just ask the grammar for the format to get from the date.
            if ($value instanceof DateTimeInterface) {
                $bindings[$key] = $value-&gt;format($grammar-&gt;getDateFormat());
            } elseif (is_bool($value)) {
                $bindings[$key] = (int) $value;
            }
        }

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



<p>ここまで引き渡してきたattributeの値が <code>DateTimeInterface</code> の実装である場合、フォーマットされた文字列で詰め込み直しています。Carbonは <code>DateTimeInterface</code> を実装しているので、ここまで来てようやく文字列に変換される、ということでした。</p>



<p>フォーマットは Grammerクラス に定義されている <code>Y-m-d H:i:s</code> になるようです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Grammer.php" data-lang="PHP"><code>    /**
     * Get the format for database stored dates.
     *
     * @return string
     */
    public function getDateFormat()
    {
        return &#39;Y-m-d H:i:s&#39;;
    }</code></pre></div>



<p>ConnectionとGrammerクラスは、DB接続とSQLの構文を抽象化しており、databases.phpで指定しているdriverによって解決されるクラスが変わります。</p>



<h2 class="wp-block-heading"><span id="toc5">おわりに</span></h2>



<p>Carbonオブジェクトを渡した場合、SQLを組み立てる最終段階で文字列に変換されることが確認出来ました。Eloquentを利用する際はどちらを使っても仕様上は問題なさそうですね。</p>



<p>ちなみに created_at と updated_at のように自動的に挿入されるカラムはCarbonオブジェクトをそのまま attributes に突っ込んでいました。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="HasTimestamps.php" data-lang="PHP"><code>    /**
     * Update the creation and update timestamps.
     *
     * @return $this
     */
    public function updateTimestamps()
    {
        $time = $this-&gt;freshTimestamp();

        $updatedAtColumn = $this-&gt;getUpdatedAtColumn();

        if (! is_null($updatedAtColumn) && ! $this-&gt;isDirty($updatedAtColumn)) {
            $this-&gt;setUpdatedAt($time);
        }

        $createdAtColumn = $this-&gt;getCreatedAtColumn();

        if (! $this-&gt;exists && ! is_null($createdAtColumn) && ! $this-&gt;isDirty($createdAtColumn)) {
            $this-&gt;setCreatedAt($time);
        }

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



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="HasTimestamps.php" data-lang="PHP"><code>    /**
     * Get a fresh timestamp for the model.
     *
     * @return \Illuminate\Support\Carbon
     */
    public function freshTimestamp()
    {
        return Date::now();
    }</code></pre></div>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】CorcelでWordPressの投稿をLaravelで使う</title>
		<link>https://wptech.kiichiro.work/81qpjbjun6/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Sun, 13 Nov 2022 16:44:14 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">http://13.115.157.198/?p=647</guid>

					<description><![CDATA[WordPressの投稿をLaravelで扱ってみます。 Corcel GitHub - corcel/corcel: Use WordPress backend with Laravel or any PHP appl [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>WordPressの投稿をLaravelで扱ってみます。</p>



<h1 class="wp-block-heading">Corcel</h1>




<a rel="noopener" href="https://github.com/corcel/corcel" title="GitHub - corcel/corcel: Use WordPress backend with Laravel or any PHP application" 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://wptech.kiichiro.work/wp-content/uploads/cocoon-resources/blog-card-cache/44ee7dbcd5b1b2946474fc25612012fd." 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 - corcel/corcel: Use WordPress backend with Laravel or any PHP application</div><div class="blogcard-snippet external-blogcard-snippet">Use WordPress backend with Laravel or any PHP application - corcel/corcel</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/corcel/corcel" 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>CorcelはEloquent ORMを利用してWordPressのデータを操作するインタフェースを提供しています。WordPresのデータベースへは直接接続することになります。<br>これまで、静的化WordPressの文脈でwp-json(WP REST API)を利用したデータ操作は見たことがありましたが、Corcelであればwp-jsonの仕様を意識することなく利用することが可能なようです。</p>



<h1 class="wp-block-heading">環境構築</h1>



<p>LaravelとWordPressの環境をそれぞれ構築する必要があります。構築が楽なのでDockerを利用します。</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><li><a href="#toc1" tabindex="0">Laravel</a></li><li><a href="#toc2" tabindex="0">WordPress</a></li></ol>
    </div>
  </div>

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



<p>Laravelのプロジェクトを新規に作成します (割愛)。</p>



<p>composerでCorcelをinstallします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>$ composer require jgrossi/corcel</code></pre></div>



<p>serveはsailで実施するとdocker-compose.ymlで必要なnetworksを定義してくれるので、それを使うのが良いかと思います。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>$ ./vendor/bin/sail up -d
$ ./vendor/bin/sail artisan sail:publish</code></pre></div>



<p>configは <code>corcel.php</code> と <code>database.php</code> を修正します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="config/corcel.php" data-lang="PHP"><code>&#39;connection&#39; =&gt; &#39;wordpress&#39;,</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="config/database.php" data-lang="PHP"><code>// &#39;connections&#39; に以下を追加
&#39;wordpress&#39; =&gt; [
    &#39;driver&#39; =&gt; &#39;mysql&#39;,
    &#39;host&#39; =&gt; env(&#39;WP_DB_HOST&#39;, &#39;127.0.0.1&#39;),
    &#39;port&#39; =&gt; env(&#39;WP_DB_PORT&#39;, &#39;3306&#39;),
    &#39;database&#39; =&gt; env(&#39;WP_DB_DATABASE&#39;, &#39;wordpress&#39;),
    &#39;username&#39; =&gt; env(&#39;WP_DB_USERNAME&#39;, &#39;wordpress&#39;),
    &#39;password&#39; =&gt; env(&#39;WP_DB_PASSWORD&#39;, &#39;&#39;),
    &#39;charset&#39; =&gt; &#39;utf8&#39;,
    &#39;collation&#39; =&gt; &#39;utf8_unicode_ci&#39;,
    &#39;prefix&#39; =&gt; &#39;wp_&#39;,
    &#39;strict&#39; =&gt; false,
    &#39;engine&#39; =&gt; null,
],</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file=".env"><code>WP_DB_CONNECTION=mysql
WP_DB_HOST=db
WP_DB_PORT=3306
WP_DB_DATABASE=wordpress
WP_DB_USERNAME=wordpress
WP_DB_PASSWORD=wordpress</code></pre></div>



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



<p>公式にWordPressのクイックスタートがあるのでそれを利用します。</p>




<a rel="noopener" href="https://docs.docker.jp/compose/wordpress.html" title="&#12463;&#12451;&#12483;&#12463;&#12473;&#12479;&#12540;&#12488;: Compose &#12392; WordPress &mdash; Docker-docs-ja 24.0 &#12489;&#12461;&#12517;&#12513;&#12531;&#12488;" 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%2Fdocs.docker.jp%2Fcompose%2Fwordpress.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">&#12463;&#12451;&#12483;&#12463;&#12473;&#12479;&#12540;&#12488;: Compose &#12392; WordPress &mdash; Docker-docs-ja 24.0 &#12489;&#12461;&#12517;&#12513;&#12531;&#12488;</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://docs.docker.jp/compose/wordpress.html" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">docs.docker.jp</div></div></div></div></a>



<p>networksは <code><strong>{Laravelのディレクトリ名}_sail</strong></code> を指定します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>version: &#39;3&#39;

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    networks:
      - external

  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - &quot;8000:80&quot;
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
    networks:
      - external
networks:
  external:
    driver: bridge
    name: {Laravelのディレクトリ名}_sail
volumes:
  db_data:</code></pre></div>



<h1 class="wp-block-heading">実行</h1>



<p>リクエストに対してWordPressの投稿を出力させてみます。コンテンツはWordPressの初期コンテンツを利用します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="routes/web.php" data-lang="PHP"><code>Route::get(&#39;posts/{post_id}&#39;, function (int $postId) {
    $post = Post::query()-&gt;find($postId);
    return view(&#39;posts.show&#39;, compact(&#39;post&#39;));
});</code></pre></div>



<p>WordPressの投稿は <code>post_content</code> にHTMLがそのまま入っているのでそのまま出力します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="resources/views/posts/show.blade.php" data-lang="PHP"><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;{{ str_replace(&#39;_&#39;, &#39;-&#39;, app()-&gt;getLocale()) }}&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;{{ $post-&gt;post_title }}&lt;/title&gt;
    &lt;body&gt;
        &lt;div&gt;
            {!! $post-&gt;post_content !!}
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre></div>



<p>ブラウザでアクセスすると、投稿を取得できていることが確認できます。</p>



<figure class="wp-block-image size-large is-style-default"><img decoding="async" src="https://wptech.kiichiro.work/wp-content/uploads/2022/11/251804846e95cfed0ddb7d673c51af77-1024x50.png" alt="ブラウザの表示"/></figure>



<p>curlするとブロックエディタ用の <code>&lt;! -- wp:paragraph --&gt;</code> も取得できていることがわかります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>$ curl http://localhost/posts/1
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;Hello world!&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div&gt;
            &lt;!-- wp:paragraph --&gt;
&lt;p&gt;WordPress へようこそ。こちらは最初の投稿です。編集または削除し、コンテンツ作成を始めてください。&lt;/p&gt;
&lt;!-- /wp:paragraph --&gt;
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre></div>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【Laravel】orderedUuidはversion4っぽいorderedなUUIDを生成している</title>
		<link>https://wptech.kiichiro.work/815pp4qdsi/</link>
		
		<dc:creator><![CDATA[むらおか]]></dc:creator>
		<pubDate>Tue, 23 Aug 2022 16:20:41 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[コードリーディング]]></category>
		<guid isPermaLink="false">http://13.115.157.198/?p=364</guid>

					<description><![CDATA[Str::orderedUuid()は先頭の48ビットでタイムスタンプで表しているため、順序が保証されるらしい。 はじめに Laravelのmigrationsでテーブルを作成する際は、 $table-&#62;id()  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Str::orderedUuid()は先頭の48ビットでタイムスタンプで表しているため、順序が保証されるらしい。</p>



<h1 class="wp-block-heading">はじめに</h1>



<p>Laravelのmigrationsでテーブルを作成する際は、 <code>$table-&gt;id()</code> としてidカラムがPKになることが多いかと思いですが、AUTO_INCREMENTだと都合が悪い場合もあります。その代替としては、GUIDをPKとする方法が一般的かと思われます。</p>



<p>ただし、アプリケーションでランダムに作成されたGUIDはそれ単体では連続性が保証されないので、<br>INSERTのオーバーヘッドが増加する可能性があります。</p>



<p>Laravelでは <code>Str::orderedUuid()</code> というメソッドでtime-orderedなUUID(version 4)を生成することが出来ます。<br>これを利用してorderedなUUIDをPKにすることで、INSERT時のデメリットを解消出来るということなのですが、</p>



<ul class="wp-block-list">
<li>そもそもUUIDなのにtime-orderedってどういうことなのか？</li>



<li>どのようにtime-orderedを実現しているのか？</li>
</ul>



<p>というところが気になったのでソースを深ぼって見ました。</p>



<h1 class="wp-block-heading">Str::orderedUuid</h1>



<p>とりあえず <code>Str:orderedUuid</code> の中身を見てみる。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Illuminate/Support/Str.php" data-lang="PHP"><code>/**
 * Generate a time-ordered UUID (version 4).
 *
 * @return \Ramsey\Uuid\UuidInterface
 */
public static function orderedUuid()
{
    if (static::$uuidFactory) {
        return call_user_func(static::$uuidFactory);
    }

    $factory = new UuidFactory;

    $factory-&gt;setRandomGenerator(new CombGenerator(
        $factory-&gt;getRandomGenerator(),
        $factory-&gt;getNumberConverter()
    ));

    $factory-&gt;setCodec(new TimestampFirstCombCodec(
        $factory-&gt;getUuidBuilder()
    ));

    return $factory-&gt;uuid4();
}</code></pre></div>



<p>RandomGeneratorにCombGeneratorを、CodecにTimestampFirstCombCodecを指定し、<code>UuidFactory::uuid4</code> を呼び出しています。RandomGeneratorとCodecがどのように使われているかは後述するとして、<code>UuidFactory::uuid4</code> の中身を見てみます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Ramsey/Uuid/UuidFactory.php" data-lang="PHP"><code>public function uuid4(): UuidInterface
{
    $bytes = $this-&gt;randomGenerator-&gt;generate(16);

    return $this-&gt;uuidFromBytesAndVersion($bytes, 4);
}</code></pre></div>



<p>ここで先程指定したRandomGeneratorのgenerateを呼び出しています。そこで生成されたバイト文字列とバージョンを示す4を引数として <code>uuidFromBytesAndVersion</code> を呼び出しています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Ramsey/Uuid/UuidFactory.php" data-lang="PHP"><code>/**
 * Returns an RFC 4122 variant Uuid, created from the provided bytes and version
 *
 * @param string $bytes The byte string to convert to a UUID
 * @param int $version The RFC 4122 version to apply to the UUID
 *
 * @return UuidInterface An instance of UuidInterface, created from the
 *     byte string and version
 *
 * @psalm-pure
 */
private function uuidFromBytesAndVersion(string $bytes, int $version): UuidInterface
{
    /** @var array $unpackedTime */
    $unpackedTime = unpack(&#39;n*&#39;, substr($bytes, 6, 2));
    $timeHi = (int) $unpackedTime[1];
    $timeHiAndVersion = pack(&#39;n*&#39;, BinaryUtils::applyVersion($timeHi, $version));

    /** @var array $unpackedClockSeq */
    $unpackedClockSeq = unpack(&#39;n*&#39;, substr($bytes, 8, 2));
    $clockSeqHi = (int) $unpackedClockSeq[1];
    $clockSeqHiAndReserved = pack(&#39;n*&#39;, BinaryUtils::applyVariant($clockSeqHi));

    $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2);
    $bytes = substr_replace($bytes, $clockSeqHiAndReserved, 8, 2);

    if ($this-&gt;isDefaultFeatureSet) {
        return LazyUuidFromString::fromBytes($bytes);
    }

    return $this-&gt;uuid($bytes);
}</code></pre></div>



<p><code>uuidFromBytesAndVersion</code> では引き渡されたバイト文字列のうち、versionとvariantを示す桁を置換しています。最後にそのバイト文字列からUuidクラスをnewして返却しています。</p>



<p>要するに <code>Str:orderedUuid</code> では、「RandomGeneratorで生成されたバイト文字列からUuidを生成している」ということのようです。</p>



<h1 class="wp-block-heading">CombGenerator</h1>



<p>CombGeneratorは <strong>COMB</strong> と呼ばれる連続性を持ったGUIDを生成します。<br>当該クラスのPhpDocに生成方法についての記述があります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>/**
 * CombGenerator generates COMBs (combined UUID/timestamp)
 *
 * The CombGenerator, when used with the StringCodec (and, by proxy, the
 * TimestampLastCombCodec) or the TimestampFirstCombCodec, combines the current
 * timestamp with a UUID (hence the name &quot;COMB&quot;). The timestamp either appears
 * as the first or last 48 bits of the COMB, depending on the codec used.
 *
 * By default, COMBs will have the timestamp set as the last 48 bits of the
 * identifier.
 * (略)
 */</code></pre></div>



<p>DeepL翻訳にそのまま突っ込んだのが以下。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><em>CombGenerator は StringCodec (そして、その代理として<br>TimestampLastCombCodec) または TimestampFirstCombCodec と共に使われる場合、CombGenerator は現在のタイムスタンプをUUIDと結合します。<br>タイムスタンプとUUIDを組み合わせます（&#8221;COMB &#8220;という名前に由来します）。タイムスタンプは<br>タイムスタンプは、使用するコーデックに応じて、COMBの最初または最後の48ビットとして表示されます。<br>デフォルトでは、COMBはタイムスタンプを識別子の最後の48ビットとして設定されます。</em></p>
</blockquote>



<p>CombGeneratorがやっていることは、COMBと呼ばれるUUIDとtimestampを組み合わせた (combined) バイト文字列を生成するところまでで、timestampの48ビットをどこに割り当てるかはCodecによりけり、ということが窺えます。以下はその処理を司るメソッドです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Ramsey/Uuid/Generator/CombGenerator.php" data-lang="PHP"><code>/**
 * @throws InvalidArgumentException if $length is not a positive integer
 *     greater than or equal to CombGenerator::TIMESTAMP_BYTES
 *
 * @inheritDoc
 */
public function generate(int $length): string
{
    if ($length &lt; self::TIMESTAMP_BYTES) {
        throw new InvalidArgumentException(
            &#39;Length must be a positive integer greater than or equal to &#39; . self::TIMESTAMP_BYTES
        );
    }

    $hash = &#39;&#39;;
    if (self::TIMESTAMP_BYTES &gt; 0 && $length &gt; self::TIMESTAMP_BYTES) {
        $hash = $this-&gt;randomGenerator-&gt;generate($length - self::TIMESTAMP_BYTES);
    }

    $lsbTime = str_pad(
        $this-&gt;converter-&gt;toHex($this-&gt;timestamp()),
        self::TIMESTAMP_BYTES * 2,
        &#39;0&#39;,
        STR_PAD_LEFT
    );

    return (string) hex2bin(
        str_pad(
            bin2hex($hash),
            $length - self::TIMESTAMP_BYTES,
            &#39;0&#39;
        )
        . $lsbTime
    );
}</code></pre></div>



<p>大まかには、以下の流れで処理されていきます。</p>



<ol class="wp-block-list">
<li>生成する文字列からTIMESTAMP_BYTESの桁数を除いた桁数の文字列を生成する</li>



<li>現在のtimestampを16進表記に置き換えた文字列を生成する</li>



<li>1の文字列の末尾に2を結合する</li>
</ol>



<h1 class="wp-block-heading">TimestampFirstCombCodec</h1>



<p>生成したUuidクラスを文字列に変換する際にCodecによるencodeが呼び出されます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Ramsey/Uuid/Uuid.php" data-lang="PHP"><code>/**
 * @psalm-return non-empty-string
 */
public function toString(): string
{
    return $this-&gt;codec-&gt;encode($this);
}</code></pre></div>



<p>ここに来てようやく TimestampFirstCombCodec が使われるのです。</p>



<p>encodeの処理自体は比較的シンプルで、</p>



<ol class="wp-block-list">
<li>CombGeneratorで生成したバイト文字列の末尾から48ビット(timestamp)と先頭の48ビットを入れ替える</li>



<li>UUIDのフォーマットに合わせて桁を区切り、 &#8220;-&#8221; でつなげる</li>
</ol>



<p>というものになっています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-php" data-file="Ramsey/Uuid/Codec/TimestampFirstCombCodec.php" data-lang="PHP"><code>/**
 * @psalm-return non-empty-string
 * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
 * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
 */
public function encode(UuidInterface $uuid): string
{
    $bytes = $this-&gt;swapBytes($uuid-&gt;getFields()-&gt;getBytes());

    return sprintf(
        &#39;%08s-%04s-%04s-%04s-%012s&#39;,
        bin2hex(substr($bytes, 0, 4)),
        bin2hex(substr($bytes, 4, 2)),
        bin2hex(substr($bytes, 6, 2)),
        bin2hex(substr($bytes, 8, 2)),
        bin2hex(substr($bytes, 10))
    );
}

/**
 * Swaps bytes according to the timestamp-first COMB rules
 */
private function swapBytes(string $bytes): string
{
    $first48Bits = substr($bytes, 0, 6);
    $last48Bits = substr($bytes, -6);

    $bytes = substr_replace($bytes, $last48Bits, 0, 6);
    $bytes = substr_replace($bytes, $first48Bits, -6);

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



<h1 class="wp-block-heading">結論</h1>



<p><code>Str::orderedUuid</code> が生成する文字列は、timestampとUUIDを組み合わせて得られる連続性が保証されたUUIDである、といえます。</p>



<p>冒頭の問には以下の通りの回答となるかと思います。</p>



<ul class="wp-block-list">
<li>そもそもUUIDなのにtime-orderedってどういうことなのか？<br>→ versionを示す桁は4になるが、UUID version 4と似て非なるもの</li>



<li>どのようにtime-orderedを実現しているのか？<br>→ 先頭48ビットでタイムスタンプを表してtime-orderedを実現している</li>
</ul>



<h1 class="wp-block-heading">おわりに</h1>



<p>正直なところ、<code>Str::orderedUuid</code> については「ソート出来るUUID」ぐらいの認識しかなく、あまり疑問を持たずに使ってしまっていました。。<br>「良くわからないものはちゃんと調べてから使う」というのは当たり前のようで案外出来ないものですね。</p>



<h1 class="wp-block-heading">参考資料</h1>



<p><strong>Laravel: The mysterious “Ordered UUID”</strong><br><a href="https://itnext.io/laravel-the-mysterious-ordered-uuid-29e7500b4f8">https://itnext.io/laravel-the-mysterious-ordered-uuid-29e7500b4f8</a></p>



<p><strong>The Cost of GUIDs as Primary Keys</strong><br><a href="https://www.informit.com/articles/printerfriendly/25862">https://www.informit.com/articles/printerfriendly/25862</a></p>



<p><strong>RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace</strong><br><a href="https://www.rfc-editor.org/rfc/rfc4122.html#section-4.1.1">https://www.rfc-editor.org/rfc/rfc4122.html#section-4.1</a></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
