shirane lab - personal notes on drupal & web application https://www.white-root.com/ ja Drupal recipes について https://www.white-root.com/blog/1671664581 <span>Drupal recipes について</span> <span><span>shirane</span></span> <span>2022/12/22(木) - 08:16</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>この記事は、<a href="https://qiita.com/advent-calendar/2022/drupal" rel=" noopener" target="_blank">Drupal Advent Calendar 2022</a> の 22 日目です。</p> <hr /><p>Drupal recipes は、Drupal サイトに特定の機能を追加できるようにする新しい仕組みです。Portland DrupalCon 2022 の Driesnote で <a href="https://www.youtube.com/watch?v=Ig676RzJbLo&amp;t=3178s" rel=" noopener" target="_blank">Starter templates</a> として紹介されたアイデアで、現在では <a href="https://www.drupal.org/project/drupal/issues/3283151" rel=" noopener" target="_blank"><strong>Drupal recipes</strong> という名前</a>で <a href="https://www.drupal.org/about/core/strategic-initiatives-distributions-and-recipes" rel=" noopener" target="_blank">Distributions &amp; Recipes initiative</a> によって開発が進められています。</p> <div class="w3-card-4 w3-panel w3-sand"> <p class="w3-small">※Drupal コミュニティの <a href="https://www.drupal.org/community-initiatives" rel=" noopener" target="_blank">initiative</a> とは、特定の関心事に基づく作業グループのことです。中でも Dries 氏が特に<a href="https://www.drupal.org/governance/core/initiatives" rel=" noopener" target="_blank">重要と感じた</a>ものは <a href="https://www.drupal.org/about/core/strategic-initiatives" rel=" noopener" target="_blank">Strategic initiatives</a> に指定されています。Distributions &amp; Recipes initiative も Startegic initiatives の1つです。</p> </div> <p>現在、<a href="https://git.drupalcode.org/project/distributions_recipes/" rel=" noopener" target="_blank">こちらのリポジトリ</a>で仕様の検討と試験的な実装による検証を繰り返している段階と思われます。全体の計画や現在の状況は、下記ページで知ることができます。</p> <ul><li><a href="https://www.drupal.org/project/ideas/issues/3274999" rel=" noopener" target="_blank">Distributions and Recipes initiative overview and roadmap</a></li> <li><a href="https://www.drupal.org/project/distributions_recipes" rel=" noopener" target="_blank">drupal.org のプロジェクトページ</a></li> <li><a href="https://git.drupalcode.org/project/distributions_recipes/-/blob/1.0.x/docs/recipe_roadmap.md" rel=" noopener" target="_blank">Drupal recipe roadmap</a></li> </ul><h2>目指すもの</h2> <p>Drupal は高度な柔軟性と拡張性を備えていますが、それを活用して特定の要件を満たす構成を実現する仕事はサイトビルダーに任されています。多くの初心者はここで行き詰まります。ですが、事前に定義された構成設定(例:SEO 対策、ブログ、ショッピングカート、等)が汎用的な「レシピ」として利用できるとしたらどうでしょう。もっと簡単にサイトを構築できると思いませんか?これが Drupal recipes の目指すところです。</p> <p>Distributions &amp; Recipes initiative としては、次のような目標を掲げています。</p> <ul><li>必要なレシピを(将来は <a href="https://www.drupal.org/project/project_browser" rel=" noopener" target="_blank">Project Browser</a> で)探せる</li> <li>同じサイトで複数のレシピを使用できる</li> <li>インストール後のサイトにもレシピを導入できる</li> <li>開始時の構成を共有する大量の複数サイトを容易に保守できる</li> <li>インストール済みのサイトを簡単に更新できる(<a href="https://www.drupal.org/project/update_helper" rel=" noopener" target="_blank">Update Helper</a> の利用)</li> <li>drupal.org と Composer によるサポートの強化</li> <li>デモ用コンテンツを同梱できる</li> </ul><p>従来、定義済みの構成設定を利用させる手段として<strong>ディストリビューション</strong>や<strong>インストールプロファイル</strong>が使われてきました。これらはサイト構成全体をインストール時の初期状態として提供します。他方、<strong>レシピ</strong>は(サイト全体ではなく)個別の機能や構成設定を提供するもので、既にインストール済みのサイトに後から追加することや、同じサイトで複数のレシピを組み合わせて利用することもできます。</p> <p>詳細については、<a href="https://www.drupal.org/u/alexpott" rel=" noopener" target="_blank">Alex Pott</a> 氏(プロジェクトリード)による DrupalCon Prague 2022 のセッションで解説とデモを見ることができます。</p> <div class="youtube w3-margin-bottom"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/uNBRLS5zTa8?start=554" title="YouTube video player" width="560"></iframe></div> <h2>サンプル</h2> <p>上記セッションのデモを参考に Drupal 10 でサンプルを作ってみました。現在、Drupal recipes の試験実装を利用するには Drupal core にパッチを適用する必要があるため、自作のインストールプロファイル「SQbase」の <a href="https://github.com/bkenro/sqbase/tree/10.0-demo-recipe" rel=" noopener" target="_blank">10.0-demo-recipe</a> ブランチとして、適用済みの D10 プロジェクトを公開しています。</p> <p>試してみるには、まず、ローカルサイトをインストールします。<br /> (自作の Drupal 実習用仮想マシン環境 <a href="https://github.com/bkenro/ffdsm" rel=" noopener" target="_blank">ffdsm</a> での例を示します)</p> <pre> <code class="language-bash">### サイトをインストールする $ cd /var/www $ git clone https://github.com/bkenro/sqbase.git -b 10.0-demo-recipe $ cd sqbase $ composer install $ drush @local sql:create -y $ drush @local site:install sqbase -y </code></pre> <p>これでローカルのサイトが立ち上がります。初期状態では、記事と基本ページという2つのコンテンツタイプが用意されています。</p> <p>このプロジェクトには、現行の Drupal recipes の試験実装、およびそれを利用したレシピのサンプル(web/recipes/myevent フォルダ)が組み込まれています。サンプルの内容は「イベント」というコンテンツタイプの追加と、関連するロールと権限を設定するものです。</p> <p>レシピの定義ファイル recipe.yml ファイルを示します。</p> <pre> <code class="language-yaml">name: 'My Event' description: 'イベントのコンテンツタイプを追加する Drupal recipe サンプル' type: 'Content type' install: - datetime_range - field - field_group - field_ui - menu_ui - node - path - text config: actions: user.role.editor: ensure_exists: label: 'イベント編集者' grantPermissions: - 'delete any event content' - 'edit any event content' </code></pre> <p>install キーは、レシピに必要なコンポーネント(モジュール)のリストです。レシピ適用時に自動インストールされます。また、config / actions キーは、Config Actions という API を利用して新しいロールと権限を追加する設定です。</p> <p>これを、いまインストールしたサイトに適用してみます。試験実装では Drupal CLI の drupal コマンドに recipe というサブコマンドが追加されるので、この引数として組み込むレシピのパスを指定します。</p> <pre> <code class="language-bash">### Webルートに移動してCLIでレシピを組み込む $ cd web $ php core/scripts/drupal recipe recipes/myevent [OK] My Event applied successfully </code></pre> <p>正常に実行されると、「イベント」コンテンツタイプ、および関連するロールと権限が追加されるはずです。</p> <p>上記は自作プロファイルを利用した例でしたが、別のインストールプロファイルでインストールしたサイトでも同様にこのレシピを適用することができます。</p> <pre> <code class="language-bash">### 最小プロファイルでインストールする例 $ drush @local site:install minimal -y ### 標準プロファイルでインストールする例 $ drush @local site:install standard -y ### Umami プロファイルでインストールする例 $ drush @local site:install demo_umami -y</code></pre> <p>どのプロファイルでインストールした場合でも、myevent レシピを適用することで、上の例と同様、「イベント」コンテンツタイプや関連するロール/権限が追加されることがわかると思います。</p> <p>このように、レシピを利用すると、サイトのインストールに使用したプロファイル(またはディストリビューション)に関係なく、既にインストール済みのサイトに対して、特定の機能や構成を追加で組み込むことができます。</p> <p>SEO 対策、ブログ機能、ショッピングカート機能、…といった汎用的な「レシピ」が豊富に提供されるようになれば、サイト要件に応じて必要なレシピを組み込むことで、高度な Web アプリケーションを簡単に構築できるようになるかもしれません。</p> <h2>おわりに</h2> <p>ディストリビューションのようにサイト全体の構成設定が一体化したモノリシックな構造ではなく、より小さい粒度の汎用的な機能や構成をレシピとして定義できるようになれば、より幅広い層の人たちが Drupal のサイトビルダーとして活躍できる可能性があります。</p> <p>Drupal core の機能をネジ・クギとするなら、レシピは目的ごとに用意された汎用的な組み立て部品、ディストリビューションはそれらをパッケージした手作りキットのようなイメージでとらえることができるでしょうか。</p> <p>他方、<a href="https://git.drupalcode.org/project/distributions_recipes/-/tree/1.0.x/docs" rel=" noopener" target="_blank">開発中のリポジトリのドキュメント</a>などを見ると、解決しなければならない課題も少なくないようで、かなり挑戦的な取り組みのように思えます。最終的に Drupal core に取り込まれる時期や、先述の目標がすべて達成されるのかどうかはまだわかりません。ただ、個人的には、ディストリビューションの作成やメンテナンスという観点から、現行の実装でもレシピは十分に有用な技術だと考えています。今後の展開に注目していきたいと思います。</p> </div> Wed, 21 Dec 2022 23:16:21 +0000 shirane 175 at https://www.white-root.com 『D9 おいしいレシピ集2』がパワーアップして商業誌に https://www.white-root.com/blog/1655685224 <span>『D9 おいしいレシピ集2』がパワーアップして商業誌に</span> <span><span>shirane</span></span> <span>2022/06/20(月) - 09:33</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>『Drupal 9 おいしいレシピ集2』がインプレスR&amp;Dさん技術の泉シリーズから商業誌として発売。今回も Drupal ユーザーに役立つ情報満載、同人誌発行後の加筆修正でさらなる充実の内容です。</p> <div class="w3-center w3-padding-16"><a href="https://www.amazon.co.jp/dp/B0B3MJC59Q" rel=" noopener" target="_blank"><img alt="『D9 おいしいレシピ集2』がパワーアップして商業誌に" class="w3-card-2 image-large" data-entity-type="file" data-entity-uuid="insert-large-b66ee333-54cc-4097-abbb-3201a7107e2b" data-insert-attach="{&quot;id&quot;:&quot;b66ee333-54cc-4097-abbb-3201a7107e2b&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="image-large" data-insert-type="image" height="480" src="/system/files/styles/large/private/2022-06/recipe2.png?itok=6V_Yxr8u" width="333" /></a></div> <p>【目次】<br /> 第1章 settings.php探訪<br /> 第2章 Webformをアンケート収集に活用しよう<br /> 第3章 Feedsモジュールの活用とDrupalから記事配信まで<br /> 第4章 Geofieldモジュールで位置情報を可視化<br /> 第5章 Drupalで理解するOAuth 2.0<br /> 第6章 インストールプロファイルを作ってみよう<br /> 第7章 Drushの独自コマンドを作ってみよう</p> <p>私は第6章の「インストールプロファイルを作ってみよう」と、今回新たに追加になった第7章「Drush の独自コマンドを作ってみよう」を書きました。<a href="/drush-commands/generate">drush generate</a> module コマンドでモジュールのひな形コードを生成したり、<a href="https://xdebug.org/" rel=" noopener" target="_blank">Xdebug</a> でステップ実行したり、<a href="/blog/1639149396">drush php:cli コマンドで PHP REPL の PsySH を使</a>って Drupal コアが提供するサービスを調査したり、調べた Drupal のサービスを <a href="https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/services-and-dependency-injection-in-drupal-8" rel=" noopener" target="_blank">DI(依存性注入)</a>してコマンドを実装したりする話です。私の担当章は毎度ニッチなネタですが(笑)ご興味ある方は是非チェックしてみていただけると嬉しいです。</p> <p>よろしくお願いいたします。</p> </div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/59" hreflang="ja">書籍</a></div> </div> </div> Mon, 20 Jun 2022 00:33:44 +0000 shirane 174 at https://www.white-root.com 『Drupal 9 おいしいレシピ集』の続編が発売! https://www.white-root.com/blog/1642601147 <span>『Drupal 9 おいしいレシピ集』の続編が発売!</span> <span><span>shirane</span></span> <span>2022/01/19(水) - 23:05</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>昨年末に商業誌版が刊行された<a href="https://nextpublishing.jp/book/14134.html" rel=" noopener" target="_blank">『Drupal 9 おいしいレシピ集』</a>ですが、早くもこの1月の<a href="https://techbookfest.org/event/tbf12" rel=" noopener" target="_blank">技術書典12</a>で続編<a href="https://techbookfest.org/product/5171268275929088" rel=" noopener" target="_blank">『Drupal 9 おいしいレシピ集2』</a>が出ます。</p> <div class="w3-center w3-padding-16"><a href="https://techbookfest.org/product/5171268275929088" rel=" noopener" target="_blank"><img alt="『Drupal 9 おいしいレシピ集』の続編が発売!" class="w3-card-2 image-large" data-entity-type="file" data-entity-uuid="insert-large-f8559418-be62-4c22-849f-d0ef0d7bf6fd" data-insert-attach="{&quot;id&quot;:&quot;f8559418-be62-4c22-849f-d0ef0d7bf6fd&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="image-large" data-insert-type="image" height="480" src="/system/files/styles/large/private/2022-01/techbookfest12.png?itok=ktTHW4Wt" width="340" /></a></div> <p>タイトなスケジュールの中、<a href="https://drupal-meetup-toyota.connpass.com/" rel=" noopener" target="_blank">Drupal Meetup 豊田支部</a>の有志が年末年始と土日の休みを捧げて(笑)書き上げた一冊です。既刊に引き続き今回も各章独立のオムニバス形式、多彩なテーマで Drupal 9 の実践テクニックを紹介しています。</p> <p>やはり目玉は計2ページの4コマ漫画。今回は「あとがき」にも<strong>にゃんコツ</strong>が登場します。まるでアニメを見終わった後のような名残惜しさが漂う…</p> <p>目次</p> <ul><li>第1章 settings.php 探訪</li> <li>第2章 Webform をアンケート収集に活用しよう</li> <li>第3章 Feeds モジュールの活用と Drupal から記事配信まで</li> <li>第4章 Geofield モジュールで位置情報を可視化</li> <li>第5章 Drupal で理解する OAuth 2.0</li> <li>第6章 インストールプロファイルを作ってみよう</li> </ul><p>私は、第6章「インストールプロファイルを作ってみよう」を担当。<a href="https://www.drupal.org/docs/configuration-management" rel=" noopener" target="_blank">Drupal の構成管理</a>と <a href="https://www.drupal.org/project/default_content" rel=" noopener" target="_blank">Default Content モジュール</a>を利⽤して、事前に⽤意した構成とコンテンツに基づいて新規サイトをインストールする例を紹介しています。</p> <p>どんなことができるのかを動画にまとめてみました。</p> <div class="youtube"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/Qbb-MjpwHAk" title="YouTube video player" width="560"></iframe></div> <p>ご興味をお持ちいただけたなら、ぜひ本をチェックしてみてください。すぐに役立つ Drupal の情報満載の一冊です。<a href="https://techbookfest.org/product/5171268275929088" rel=" noopener" target="_blank">技術書典</a>、<a href="https://drupaltoyota.booth.pm/items/3593247" rel=" noopener" target="_blank">BOOTH</a> のほか、<a href="https://www.amazon.co.jp/dp/B09QQRQ9SW" rel=" noopener" target="_blank">Kindle</a> でもお読みいただけます。</p> <p>よろしくお願いいたします。</p> </div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/59" hreflang="ja">書籍</a></div> </div> </div> Wed, 19 Jan 2022 14:05:47 +0000 shirane 173 at https://www.white-root.com .ssh/config を変更したら vagrant ssh が動かなくなったので https://www.white-root.com/blog/1642133217 <span>.ssh/config を変更したら vagrant ssh が動かなくなったので</span> <span><span>shirane</span></span> <span>2022/01/14(金) - 13:06</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>Windows の Git をアップデート後、git でリモートリポジトリにアクセスしようとしたらエラーになった。</p> <pre> <code>&gt;git fetch origin Unable to negotiate with ~ port ~: no matching host key type found. Their offer: ssh-rsa fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.</code></pre> <p>~/.ssh/config に下記エントリを追加して回避すると、</p> <pre> <code>HostkeyAlgorithms +ssh-rsa PubkeyAcceptedAlgorithms +ssh-rsa</code></pre> <p>今度は vagrant ssh コマンドが動かなくなった。</p> <pre> <code>&gt;vagrant ssh C:\\Users\\shirane/.ssh/config: terminating, 1 bad configuration options</code></pre> <p>困った。どうするか。</p> <p><strong>回避策1</strong></p> <p>vagrant ssh は次のようにする:</p> <pre> <code>&gt;vagrant ssh -- -F /dev/null</code></pre> <p><strong>回避策2</strong></p> <p>空の config ファイルを作って Vagrantfile で指定する。たとえば、empty_config という名前の空のファイルを作り、次の記述を</p> <pre> <code class="language-ruby">config.ssh.config = "empty_config"</code></pre> <p>Vagrantfile に追加する。</p> <p>1と2のどちらかで、とりあえず git も vagrant ssh も使えるようになった。</p> <h2>参考資料</h2> <ul><li><a href="https://github.com/hashicorp/vagrant/issues/11709" rel=" noopener" target="_blank">Outdated SSH client in app image Linux package</a></li> <li><a href="https://github.com/hashicorp/vagrant/issues/10601" rel=" noopener" target="_blank">new ssh config directive "include" breaks "vagrant ssh"</a></li> <li><a href="https://www.vagrantup.com/docs/vagrantfile/ssh_settings#config-ssh-config" rel=" noopener" target="_blank">config.ssh.config</a></li> </ul></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/61" hreflang="ja">ffdsm</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/9" hreflang="ja">Vagrant</a></div> </div> </div> Fri, 14 Jan 2022 04:06:57 +0000 shirane 172 at https://www.white-root.com DruxtJS について https://www.white-root.com/blog/1640128281 <span>DruxtJS について</span> <span><span>shirane</span></span> <span>2021/12/22(水) - 08:11</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>この記事は <a href="https://qiita.com/advent-calendar/2021/drupal" rel=" noopener" target="_blank">Drupal Advent Calendar 2021</a> の22日目です。</p> <hr /><p><a href="https://druxtjs.org/" rel=" noopener" target="_blank">DruxtJS</a> は、<a href="https://nuxtjs.org/" rel=" noopener" target="_blank">Nuxt.js</a> と Drupal を組み合わせたフル デカップルのサイトや <a href="https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3" rel=" noopener" target="_blank">SPA</a> を構築するためのフレームワークです。Drupal をコンテンツ リポジトリとして利用しながら、Nuxt.js ベースの SPA や SSG サイトを構築することができます。</p> <blockquote> <p>TL;DR: Druxt = DRUpal + nUXT.</p> </blockquote> <p>Drupal と <a href="https://jp.vuejs.org/" rel=" noopener" target="_blank">Vue.js</a> でデカップル構成のシステムを構築する際の有力な選択肢になりそうです。ここでは、DruxtJS 公式サイトの資料に従って、デモサイトの動作とクイックスタートの手順を確認してみた内容をレポートします。</p> <h2>Umami デモ</h2> <p>Drupal のデモ用インストール プロファイルである <a href="https://www.drupal.org/docs/umami-drupal-demonstration-installation-profile" rel=" noopener" target="_blank">Umami</a> を利用したデモが用意されています。GitHub 上に、Drupal プロジェクト(バックエンド)と Nuxt プロジェクト(フロント)のリポジトリが公開されています。</p> <ul><li><a href="https://github.com/druxt/demo-api.druxtjs.org" rel="nofollow noopener noreferrer" target="_blank">Umami demo Drupal リポジトリ</a></li> <li><a href="https://github.com/druxt/demo.druxtjs.org" rel="nofollow noopener noreferrer" target="_blank">Umami demo Nuxt リポジトリ</a></li> </ul><p>まずは、これをインストールしてみました。どちらも <a href="https://www.gitpod.io/docs" rel=" noopener" target="_blank">Gitpod</a> 上で動かせるように<a href="https://www.gitpod.io/docs/configure" rel=" noopener" target="_blank">構成されて</a>いますが、今回は<a href="https://github.com/bkenro/ffdsm" rel=" noopener" target="_blank">ローカルの仮想マシン環境</a>で動かすことにします。なお、Gitpod  を利用した Drupal の開発環境については、<a href="https://qiita.com/advent-calendar/2021/drupal" rel=" noopener" target="_blank">Drupal Advent Calendar 2021</a> の5日目<a href="https://qiita.com/snize/items/13f34c0d9abc87dc97b5" rel=" noopener" target="_blank">「最短でDrupal開発環境(お試し環境)を手に入れる2021」</a>に解説があります。</p> <h3>Drupal(バックエンド)側</h3> <p>通常の Drupal サイトの要領でインストールします。ここでは、Drush とサイトエイリアス @local の定義を追加して次のようにローカル サイトを立ち上げました。</p> <pre> <code class="language-bash">$ cd /var/www $ git clone https://github.com/druxt/demo-api.druxtjs.org.git $ cd demo-api.druxtjs.org $ composer require drush/drush $ drush @local sql:create $ drush @local site:install demo_umami </code></pre> <p>なお、サイトエイリアスの使い方については、先日発売された<a href="/blog/1639652838">インプレスR&amp;D「技術の泉」シリーズの商業誌『Drupal 9 おいしいレシピ集』</a>にまとめましたので、是非そちらもチェックしてみてください。</p> <p>ここでは、上記プロジェクトの web サブディレクトリを umami.internal という仮想ホストとして構成しました。仮想ホストの設定手順は、<a href="https://youtu.be/2pllnb6cyCw" rel=" noopener" target="_blank">こちらの動画</a>と<a href="https://sqt.jp/course/1844" rel=" noopener" target="_blank">トレーニング教材</a>にまとめています。</p> <p><span id="cke_bm_138S" style="display: none;"> </span>正常にインストールできたら、DruxtJS Umami モジュールを有効にします。</p> <pre> <code class="language-bash">$ drush pm:enable druxt_umami </code></pre> <p><a href="https://www.drupal.org/project/gin" rel=" noopener" target="_blank">管理用テーマの Gin </a>がデフォルトに設定された Umami サイトが立ち上がりました。いつもの umami テーマとは違う外観で、ちょっと新鮮な感じです。</p> <p><img alt="インストールした Umami サイト" data-entity-type="file" data-entity-uuid="insert-image-7baa6c10-9b9a-4945-9ad2-4f12afe5b354" data-insert-attach="{&quot;id&quot;:&quot;7baa6c10-9b9a-4945-9ad2-4f12afe5b354&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="" data-insert-type="image" height="874" src="/system/files/2021-12/umami1.png" width="966" /></p> <p>以下では、このサイト  http://umami.internal/ をリポジトリ サーバーとして利用します。外部から DruxtJS のリソースへのアクセスができるように、匿名ユーザーに<a href="/drush-commands/role_perm_add">アクセス権を設定</a>しておきます。</p> <pre> <code class="language-bash">$ drush role-add-perm anonymous 'access druxt resources' </code></pre> <h3>Nuxt(フロント)側</h3> <p>次にフロント側の Nuxt プロジェクトをインストールします。今回使用した仮想マシン <a href="https://github.com/bkenro/ffdsm" rel=" noopener" target="_blank">ffdsm</a> には Node.js が入っていないので、まずインストールしておきます。ffdsm は Ubuntu 20.04 ベースなので、Qiita の記事<a href="https://qiita.com/seibe/items/36cef7df85fe2cefa3ea" rel=" noopener" target="_blank">「Ubuntuに最新のNode.jsを難なくインストールする」</a>を参考にさせていただきました。</p> <pre> <code class="language-bash">$ sudo apt install -y nodejs npm $ sudo npm install n -g $ sudo n stable $ sudo apt purge -y nodejs npm $ exec $SHELL -l</code></pre> <p>上記手順で無事インストールされました。</p> <pre> <code class="language-bash">$ node -v v16.13.1 $ npx -v 8.1.2</code></pre> <p>フロント側のプロジェクトはホームディレクトリにクローンした後、ディレクトリを移動して、nuxt.config.js ファイルをエディタで開きます。</p> <pre> <code class="language-bash">$ cd ~ $ git clone https://github.com/druxt/demo.druxtjs.org.git $ cd demo.druxtjs.org $ vi nuxt.config.js </code></pre> <p>119行目付近にある baseUrl の値を、先に立ち上げたバックエンド側の URL に設定します。</p> <pre> <code class="language-javascript">~ // Druxt Configuration druxt: { baseUrl: 'http://umami.internal', blocks: { ~</code></pre> <p>パッケージをインストールして起動します。今回は仮想マシン上で動かすので、ホスト OS 側からアクセスできるように環境変数 HOST を 0.0.0.0 に設定しておきます。</p> <pre> <code class="language-bash">$ npm install $ HOST=0.0.0.0 npm run dev … ✔ Druxt schema generated 06:26:47 ✔ Nuxt files generated 06:26:48 ✔ Client Compiled successfully in 53.45s ✔ Server Compiled successfully in 38.24s ℹ Waiting for file changes 06:27:45 ℹ Memory usage: 316 MB (RSS: 497 MB) 06:27:45 ℹ Listening on: http://10.0.2.15:3000/ </code></pre> <p>DeprecationWarning の警告が表示されますが、ポート 3000 の待ち受けで Web サーバーが起動しました。ブラウザで http://umami.internal:3000 にアクセスしてみます。</p> <p><img alt="DruxtJS で動作する Umami サイト" data-entity-type="file" data-entity-uuid="insert-image-7e0b94de-b931-409d-82ab-5146ece91174" data-insert-attach="{&quot;id&quot;:&quot;7e0b94de-b931-409d-82ab-5146ece91174&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="" data-insert-type="image" height="1280" src="/system/files/2021-12/capture1.png" style="border:1px solid #ccc;" width="581" /></p> <p>一見、普通の Umami サイトのように見えますが、Nuxt ベースのアプリケーションなので、<a href="https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja" rel=" noopener" target="_blank">Vue.js devtools</a> でコンポーネントの階層を確認することができます。</p> <p><img alt="Vue.js devtools でコンポーネント階層を見る" data-entity-type="file" data-entity-uuid="insert-image-2ea5e290-d8dd-4d71-a764-1eeca1891488" data-insert-attach="{&quot;id&quot;:&quot;2ea5e290-d8dd-4d71-a764-1eeca1891488&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="" data-insert-type="image" height="920" src="/system/files/2021-12/capture2.png" width="1075" /></p> <p>設定が不十分なためか、パスが未解決になるリンクもありますが、おなじみの Umami サイトが Nuxt アプリケーションとして再現される様子はインパクトがあります。</p> <h3>フロント側を自作してみる</h3> <p>今度は、Nuxt のプロジェクトを自分で作ってアクセスしてみます。プロジェクト名は druxt にしました。</p> <pre> <code class="language-bash">$ cd ~ $ npx create-nuxt-app druxt create-nuxt-app v4.0.0 ✨ Generating Nuxt.js project in druxt ? Project name: druxt ? Programming language: JavaScript ? Package manager: Npm ? UI framework: None ? Nuxt.js modules: (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection) ? Linting tools: (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection) ? Testing framework: None ? Rendering mode: Universal (SSR / SSG) ? Deployment target: Server (Node.js hosting) ? Development tools: (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection) ? What is your GitHub username? ? Version control system: None </code></pre> <p>公式の <a href="https://druxtjs.org/modules/site/getting-started/" rel=" noopener" target="_blank">Getting started with DraxtSite</a> に従って、使用するモジュールのインストールと設定を行います。</p> <pre> <code class="language-bash">$ cd druxt $ npm i druxt-site ~ $ vi nuxt.config.js</code></pre> <p>nuxt.config.js ファイルの最後の部分を次のように編集します。</p> <pre> <code class="language-javascript">~ // Modules: https://go.nuxtjs.dev/config-modules modules: [ 'druxt-site' ], druxt: { baseUrl: 'http://umami.internal' }, // Build Configuration: https://go.nuxtjs.dev/config-build build: { } } </code></pre> <p>生成されたプロジェクトには、pages ディレクトリの下にデフォルトの index.vue が生成されていますが、とりあえずファイル名を変更して無効にしておきます。</p> <pre> <code class="language-bash">$ cd pages $ mv index.vue index.vue.org $ cd ..</code></pre> <p>この状態で実行してみます。</p> <pre> <code class="language-bash">$ HOST=0.0.0.0 npm run dev &gt; druxt@1.0.0 dev &gt; nuxt ℹ [HPM] Proxy created: /sites/default/files -&gt; http://umami.internal 07:16:52 ╭────────────────────────────────────────╮ │ │ │ Nuxt @ v2.15.8 │ │ │ │ ▸ Environment: development │ │ ▸ Rendering: server-side │ │ ▸ Target: server │ │ │ │ Listening: http://10.0.2.15:3000/ │ │ │ │ Druxt @ v0.15.0 │ │ API: http://umami.internal/jsonapi │ │ │ ╰────────────────────────────────────────╯ ℹ Preparing project for development 07:16:54 ℹ Initial build may take a while 07:16:54 ℹ Discovered Components: .nuxt/components/readme.md 07:16:54 ✔ Builder initialized 07:16:54 ✔ Druxt schema generated 07:16:55 ✔ Nuxt files generated 07:16:55 ✔ Client Compiled successfully in 26.63s ✔ Server Compiled successfully in 23.81s ℹ Waiting for file changes 07:17:25 ℹ Memory usage: 226 MB (RSS: 345 MB) 07:17:25 ℹ Listening on: http://10.0.2.15:3000/ </code></pre> <p>無事起動したので、ブラウザでアクセスしてみます。</p> <p><img alt="DruxtSite のデフォルト表示" data-entity-type="file" data-entity-uuid="insert-image-f0646562-96d0-4f88-bd56-cc06e3872686" data-insert-attach="{&quot;id&quot;:&quot;f0646562-96d0-4f88-bd56-cc06e3872686&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="" data-insert-type="image" height="785" src="/system/files/2021-12/capture3.png" width="826" /></p> <p>pages のほか、layouts や components の各ディレクトリも空の状態なので、この場合は <a href="https://druxtjs.org/api/packages/site/components/DruxtSite" rel=" noopener" target="_blank">DruxtSite</a> のデフォルトの出力として、利用可能なブロック コンポーネントがすべて表示されます。必要な情報が不足しているブロックは、枠線の中にコンポーネントのオプションとブロックの設定が表示されるようです。</p> <p>バックエンド側から JSON:API で提供される情報に基づいて、フロント側で自動的にコンポーネントのインスタンスが用意されるので、それらを利用して、<a href="https://druxtjs.org/modules" rel=" noopener" target="_blank">API のドキュメント</a>を見つつレイアウトやページをコーディングしていく流れになるのかなと思います。このあたりは、筆者に Nuxt 力がないため、Theming の具体的な検証には至りませんでした。</p> <h2>Storybook</h2> <p>最後に、Storybook を入れてみました。</p> <pre> <code class="language-bash">$ npm i @nuxtjs/storybook $ npx nuxt storybook</code></pre> <p>コンポーネントとして提供されるブロック、メニュー、エンティティ、ビューなどがカタログ化され、ブラウズできます。</p> <p><img alt="Storybook" data-entity-type="file" data-entity-uuid="insert-image-7fa382f6-f03f-4575-bfd1-d5ec10e87f57" data-insert-attach="{&quot;id&quot;:&quot;7fa382f6-f03f-4575-bfd1-d5ec10e87f57&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="" data-insert-type="image" height="813" src="/system/files/2021-12/capture4.png" width="984" /></p> <h2>まとめ</h2> <p>DruxtJS を利用したサイト作成について、導入のところを簡単に試してみました。モジュールを入れて動かすだけで、Drupal バックエンドが提供する各種ブロックが自動的にコンポーネントとして登録され、DruxtJS の API を通じて利用できるようになるので、デカップル構成におけるサイト制作がかなり楽になるのではと思いました。</p> <p>DruxtJS には、他にも <a href="https://druxtjs.org/guide/proxy" rel=" noopener" target="_blank">Proxy</a> や <a href="https://druxtjs.org/guide/client" rel=" noopener" target="_blank">JSON:API Client</a> などの API があり、これらを利用してフォーム送信や検索といったアプリケーション機能の実装もサポートされるようです。このあたりは、Nuxt 力をつけてから改めて検証してみたいと思います。</p> <p>より網羅的な紹介は、DrupalCon Europe 2021 のセッション<a href="https://youtu.be/2YWJeT_synI" rel=" noopener" target="_blank">「MAKERS &amp; BUILDERS - DruxtJS 101- Fully Decoupled Drupal with JSON-API and Nuxtjs」</a>をご覧になるのが良いと思います。</p> <div class="youtube"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/2YWJeT_synI" title="YouTube video player" width="560"></iframe></div> <p>大変興味深い内容なので、おすすめです。</p> <h2>参考資料</h2> <ul><li><a href="https://druxtjs.org/" rel=" noopener" target="_blank">DruxtJS</a></li> <li><a href="https://www.drupal.org/project/druxt" rel=" noopener" target="_blank">DruxtJS</a>(drupal.org のプロジェクトページ)</li> <li><a href="https://github.com/druxt/quickstart-druxt-site" rel="nofollow noopener noreferrer" target="_blank">Quickstart DruxtSite リポジトリ</a></li> <li><a href="https://github.com/druxt/demo.druxtjs.org" rel="nofollow noopener noreferrer" target="_blank">Umami demo Nuxt リポジトリ</a></li> <li><a href="https://github.com/druxt/demo-api.druxtjs.org" rel="nofollow noopener noreferrer" target="_blank">Umami demo Drupal リポジトリ</a></li> <li><a href="https://qiita.com/seibe/items/36cef7df85fe2cefa3ea" rel=" noopener" target="_blank">Ubuntuに最新のNode.jsを難なくインストールする</a></li> <li><a href="https://blog.mintsu-dev.com/posts/2020-08-04-virtualbox-nuxt/" rel=" noopener" target="_blank">Nuxt.js の起動時に外部アクセス可能なIPを指定する</a></li> </ul></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/64" hreflang="ja">DruxtJS</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/61" hreflang="ja">ffdsm</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/65" hreflang="ja">デカップルド</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/66" hreflang="ja">ヘッドレス</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/67" hreflang="ja">Nuxt</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/68" hreflang="ja">Vue</a></div> </div> </div> Tue, 21 Dec 2021 23:11:21 +0000 shirane 170 at https://www.white-root.com Drupal 9 おいしいレシピ集がパワーアップしてインプレスR&D「技術の泉シリーズ」から発売 https://www.white-root.com/blog/1639652838 <span>Drupal 9 おいしいレシピ集がパワーアップしてインプレスR&amp;D「技術の泉シリーズ」から発売</span> <span><span>shirane</span></span> <span>2021/12/16(木) - 20:07</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>技術書典11の同人誌『Drupal 9 おいしいレシピ集』が大幅にパワーアップして、インプレスR&amp;D「技術の泉シリーズ」の商業誌として発売されます。</p> <p>私は4章「Drushとサイトエイリアスで楽々サイト管理」を担当。ローカル、Docker、リモートの各環境で稼働する Drupal サイトに対し、簡潔で直感的に種々の Drush コマンドを実行できるようにする手法を紹介しています。リモートサイトの操作や環境間のデータコピーなど、Drush を使ったサイト管理作業がグッと楽になること請け合いです。</p> <div class="w3-center w3-padding-16"><a href="https://www.amazon.co.jp/Drupal-%E3%81%8A%E3%81%84%E3%81%97%E3%81%84%E3%83%AC%E3%82%B7%E3%83%94%E9%9B%86-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%88NextPublishing%EF%BC%89-Meetup-%E8%B1%8A%E7%94%B0%E6%94%AF%E9%83%A8/dp/4295600091/ref=sr_1_1?qid=1639231410&amp;s=books&amp;sr=1-1&amp;text=Drupal+Meetup+%E8%B1%8A%E7%94%B0%E6%94%AF%E9%83%A8" rel=" noopener" target="_blank"><img alt="Drupal 9 おいしいレシピ集 (技術の泉シリーズ)発売" class="w3-card-2 image-large" data-entity-type="file" data-entity-uuid="insert-large-06706667-24aa-4f18-9b1c-d4c0568b8e47" data-insert-attach="{&quot;id&quot;:&quot;06706667-24aa-4f18-9b1c-d4c0568b8e47&quot;,&quot;attributes&quot;:{&quot;alt&quot;:[&quot;alt&quot;,&quot;description&quot;],&quot;title&quot;:[&quot;title&quot;]}}" data-insert-class="image-large" data-insert-type="image" height="480" src="/system/files/styles/large/private/2021-12/recipe1ex.png?itok=_BVed16_" width="339" /></a></div> <p>他にも、オープンソース CMS の Drupal 9 に関する実践ノウハウ&テクニック集が満載。各章完結オムニバス形式、章末の4コマ漫画にご注目!</p> <p>【目次】</p> <ul><li>第1章 GitLab.comで手軽に始めるCI環境構築</li> <li>第2章 オンプレミスで自分だけのCI環境構築</li> <li>第3章 モジュール開発でCI実践</li> <li>第4章 Drushとサイトエイリアスで楽々サイト管理</li> <li>第5章 Groupモジュールで会員限定サイトを構築</li> <li>第6章 JamstackでヘッドレスDrupalを爆速構築</li> <li>第7章 Webformで簡単フォーム作成</li> <li>第8章 MauticとDrupalで行う自動連携</li> </ul></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/5" hreflang="ja">Drupal9</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/59" hreflang="ja">書籍</a></div> </div> </div> Thu, 16 Dec 2021 11:07:18 +0000 shirane 171 at https://www.white-root.com drush php:cli と psysh について https://www.white-root.com/blog/1639149396 <span>drush php:cli と psysh について</span> <span><span>shirane</span></span> <span>2021/12/11(土) - 00:16</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>この記事は <a href="https://qiita.com/advent-calendar/2021/drupal" rel=" noopener" target="_blank">Drupal Advent Calendar 2021</a> の11日目です。</p> <hr /><p>ちょっとしたコードを CLI で動かしたい時に便利な環境が <a href="https://ja.wikipedia.org/wiki/REPL" rel=" noopener" target="_blank">REPL</a> です。Drupal 9 では、Drush の <a href="https://www.white-root.com/drush-commands/php_cli" rel=" noopener" target="_blank">php:cli コマンド</a>で <a href="https://psysh.org/" rel=" noopener" target="_blank">PsySh</a> という PHP の REPL が利用できます。</p> <h2>インストールと実行</h2> <p>Drush の依存関係に PsySh が含まれているので、Composer で Drush を入れるだけで使えます。</p> <p><s>ただし、2021年12月5日にリリースされた最新の <a href="https://github.com/bobthecow/psysh/releases/tag/v0.11.0" rel=" noopener" target="_blank">PsySh v0.11</a> が導入されると、現状の Drush では<a href="https://github.com/drush-ops/drush/issues/4916" rel=" noopener" target="_blank">エラーが発生</a>します。このため、いま現在 Drush の新規インストールやアップデートを行うと php:cli コマンドが動作しない状態に陥る可能性があります。</s></p> <p><s>修正のプルリクが出ていますが、反映されるまでの回避策として、PsySh の旧 0.10 版を手動で入れる方法が考えられます。<a href="https://github.com/bobthecow/psysh/releases/tag/v0.10.12" rel=" noopener" target="_blank">PsySh v0.10.12</a> を上書きインストールする例を示します。</s></p> <pre> <s> <code class="language-bash">$ cd &lt;プロジェクトのルート&gt; $ curl -L https://github.com/bobthecow/psysh/archive/refs/tags/v0.10.12.tar.gz | tar xzf - --strip=1 -C vendor/psy/psysh</code></s></pre> <p><strong>追記(2021-12-17)</strong><br /><a href="https://github.com/drush-ops/drush/releases/tag/10.6.2" rel=" noopener" target="_blank">Drush 10.6.2</a> で psysh への依存関係が次のように変更されて解決しました。</p> <pre> <code class="language-json">"psy/psysh": "&gt;=0.6 &lt;0.11",</code></pre> <p>Drush をアップデートしてみたところ、psysh がダウングレードされ、drush php:cli コマンドで正常に対話シェルが起動することを確認しました。</p> <pre> <code class="language-bash">$ composer update drush/drush -w Loading composer repositories with package information Updating dependencies Lock file operations: 0 installs, 2 updates, 0 removals - Upgrading drush/drush (10.6.1 =&gt; 10.6.2) - Downgrading psy/psysh (v0.11.0 =&gt; v0.10.12) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 0 installs, 2 updates, 0 removals - Downgrading psy/psysh (v0.11.0 =&gt; v0.10.12): Extracting archive - Upgrading drush/drush (10.6.1 =&gt; 10.6.2): Extracting archive Package doctrine/reflection is abandoned, you should avoid using it. Use roave/better-reflection instead. Package webmozart/path-util is abandoned, you should avoid using it. Use symfony/filesystem instead. Generating autoload files 45 packages you are using are looking for funding. Use the `composer fund` command to find out more! $</code></pre> <p>導入されている PsySh のバージョンが 0.10.x であれば正常に起動します。</p> <pre> <code>$ drush php:cli Psy Shell v0.10.12 (PHP 7.4.3 — cli) by Justin Hileman DEMO site (Drupal 9.3.0) &gt;&gt;&gt;</code></pre> <p>起動したら、プロンプト(&gt;&gt;&gt;)に PHP コードを入力して、変数、関数、クラスなどを定義できます。</p> <pre> <code>&gt;&gt;&gt; $msg = 'Hello!'; =&gt; "Hello!" &gt;&gt;&gt; function greet($peer) { ... echo "Hi, $peer!"; ... return $peer; ... } &gt;&gt;&gt; class Person { ... private $name = ''; ... ... public function __construct($name) { ... $this-&gt;name = $name; ... } ... ... public function greet($peer) { ... echo "Hi $peer, I'm $this-&gt;name!"; ... return $this-&gt;name; ... } ... } &gt;&gt;&gt; </code></pre> <p>式を入力すると評価結果が表示され、ステートメントを入力すると実行されます。</p> <pre> <code>&gt;&gt;&gt; $msg =&gt; "Hello!" &gt;&gt;&gt; greet("Jiro"); Hi, Jiro!⏎ =&gt; "Jiro" &gt;&gt;&gt; $taro = new Person("Taro"); =&gt; Person {#4524} &gt;&gt;&gt; $taro-&gt;greet("Jiro"); Hi Jiro, I'm Taro!⏎ =&gt; "Taro" &gt;&gt;&gt; </code></pre> <h2>PsySh の機能</h2> <p><a href="https://github.com/bobthecow/psysh/wiki" rel=" noopener" target="_blank">PsySh manual</a> に詳しい説明があります。以下、一部を紹介します。</p> <h3>マジック変数</h3> <p>最後に使用した値が情報の種類別に保持されており、これを<a href="https://github.com/bobthecow/psysh/wiki/Magic-variables" rel=" noopener" target="_blank">マジック変数</a>($ で始まる定義済みの変数)で取得することができます。</p> <pre> <code>&gt;&gt;&gt; $_ // 最後の結果を保持するマジック変数 =&gt; "Taro" &gt;&gt;&gt; greet('Hanako'); Hi, Hanako!⏎ =&gt; "Hanako" &gt;&gt;&gt; $_ =&gt; "Hanako" &gt;&gt;&gt; </code></pre> <h3>PsySh コマンド</h3> <p>PsySh は種々の<a href="https://github.com/bobthecow/psysh/wiki/Commands" rel=" noopener" target="_blank">組み込みコマンド</a>を備えています。たとえば、ls コマンドを実行すると、ローカル変数やインスタンス等の一覧が表示されます。</p> <pre> <code>&gt;&gt;&gt; ls Variables: $container, $msg, $taro &gt;&gt;&gt; ls -al Variables: $container Drupal\Core\DependencyInjection\Container {#768 …620} $msg "Hello!" $taro Person {#4524} $_ "Hanako" $__out "Hi, Hanako!" </code></pre> <p>各コマンドの詳しい情報は、help コマンドで確認できます。</p> <pre> <code>&gt;&gt;&gt; help ls Usage: ls [--vars] [-c|--constants] [-f|--functions] [-k|--classes] [-I|--interfaces] [-t|--traits] [--no-inherit] [-p|--properties] [-m|--methods] [-G|--grep GREP] [-i|--insensitive] [-v|--invert] [-g|--glob&gt; Aliases: dir Arguments: target A target class or object to list. Options: --vars Display variables. --constants (-c) Display defined constants. --functions (-f) Display defined functions. --classes (-k) Display declared classes. --interfaces (-I) Display declared interfaces. --traits (-t) Display declared traits. --no-inherit Exclude inherited methods, properties and constants. --properties (-p) Display class or object properties (public properties by default). --methods (-m) Display class or object methods (public methods by default). --grep (-G) Limit to items matching the given pattern (string or regex). --insensitive (-i) Case-insensitive search (requires --grep). --invert (-v) Inverted search (requires --grep). --globals (-g) Include global variables. --internal (-n) Limit to internal functions and classes. --user (-u) Limit to user-defined constants, functions and classes. --category (-C) Limit to constants in a specific category (e.g. "date"). --all (-a) Include private and protected methods and properties. --long (-l) List in long format: includes class names and method signatures. --help (-h) Display this help message. Help: List variables, constants, classes, interfaces, traits, functions, methods, and properties. Called without options, this will return a list of variables currently in scope. If a target object is provided, list properties, constants and methods of that target. If a class, interface or trait name is passed instead, list constants and methods on that class. e.g. &gt;&gt;&gt; ls &gt;&gt;&gt; ls $foo &gt;&gt;&gt; ls -k --grep mongo -i &gt;&gt;&gt; ls -al ReflectionClass &gt;&gt;&gt; ls --constants --category date &gt;&gt;&gt; ls -l --functions --grep /^array_.*/ &gt;&gt;&gt; ls -l --properties new DateTime() </code></pre> <p>dump コマンドは、オブジェクトや基本型の値をダンプします。</p> <pre> <code>&gt;&gt;&gt; dump -a $taro Person {#4524 -name: "Taro", } &gt;&gt;&gt; </code></pre> <p>doc コマンドを使用すると、PHP のオブジェクト、クラス、定数、メソッド、プロパティのドキュメントを閲覧できます。ただし、事前に <a href="https://github.com/bobthecow/psysh/wiki/PHP-manual" rel=" noopener" target="_blank">PHP マニュアルをダウンロードしてインストールしておく</a>必要があります。日本語版をインストールする例を示します。</p> <pre> <code class="language-bash">$ wget http://psysh.org/manual/ja/php_manual.sqlite -P ~/.local/share/psysh</code></pre> <p>以後は、調べたいものを引数に指定して doc コマンドを実行すると、そのマニュアルが表示されます。</p> <pre> <code class="language-bash">&gt;&gt;&gt; doc implode function implode($glue, $pieces) Description: 配列要素を文字列により連結する 配列の要素を $glue 文字列で連結します。 implode()は、歴史的な理由により、引数をどちら の順番でも受けつけることが可能です。しかし、 explode() との統一性の観点からは、 ドキュメントに記述された引数の順番を使用しないことは推奨されま せん。 Param: string $glue デフォルトは空文字列です。 array $pieces 連結したい文字列の配列。 Return: string すべての配列要素の順序を変えずに、各要素間に $glue 文字列をはさんで 1 つの文字列にして返し ます。 See Also: * explode() * preg_split() * http_build_query() &gt;&gt;&gt; </code></pre> <h3>シェルコマンドの実行</h3> <p>PsySh の中からシェルのコマンドを実行することもできます。実行するコマンドラインをバッククォートで囲みます。コマンドの実行結果は、文字列のリテラル("〜")として表示されます。</p> <pre> <code class="language-apache">&gt;&gt;&gt; `pwd` =&gt; "/var/www/hoge/web\n" &gt;&gt;&gt; `ls -l` =&gt; """ total 84\n -rw-rw-r-- 1 vagrant vagrant 315 Dec 8 12:05 autoload.php\n drwxrwxr-x 1 vagrant vagrant 4096 Nov 24 23:27 core\n -rw-rw-r-- 1 vagrant vagrant 1507 Nov 27 17:27 example.gitignore\n -rw-rw-r-- 1 vagrant vagrant 549 Nov 27 17:27 index.php\n -rw-rw-r-- 1 vagrant vagrant 94 Nov 27 17:27 INSTALL.txt\n drwxrwxr-x 1 vagrant vagrant 4096 Dec 8 12:05 modules\n drwxrwxr-x 1 vagrant vagrant 4096 Nov 27 17:27 profiles\n -rw-r--r-- 1 vagrant vagrant 12484 Dec 8 21:14 q\n -rw-rw-r-- 1 vagrant vagrant 3205 Nov 27 17:27 README.md\n -rw-rw-r-- 1 vagrant vagrant 1586 Nov 27 17:27 robots.txt\n drwxrwxr-x 1 vagrant vagrant 4096 Dec 8 11:59 sites\n drwxrwxr-x 1 vagrant vagrant 4096 Nov 27 17:27 themes\n -rw-r--r-- 1 vagrant vagrant 12484 Dec 8 14:28 t q\n -rw-rw-r-- 1 vagrant vagrant 804 Nov 27 17:27 update.php\n -rw-rw-r-- 1 vagrant vagrant 4016 Nov 27 17:27 web.config\n """ &gt;&gt;&gt; </code></pre> <h2>Drupal における利用</h2> <p>Drush から起動した PsySh のプロンプトは、Drupal で必要なファイルがすべて読み込まれた状態になっており、モジュール内のコードと同じように Drupal API を呼び出してサイトの操作や情報取得ができます。</p> <pre> <code>// Drupal の構成設定 API でサイト名を取得する例 &gt;&gt;&gt; \Drupal::config('system.site')-&gt;get('name'); =&gt; "DEMO site" // settings.php で設定された $settings['hash_salt'] の値を取得する例 &gt;&gt;&gt; \Drupal\Core\Site\Settings::get('hash_salt'); =&gt; "7bb8S5Y7pZ4tvwciwBR8jsk0Ol2_Piy38vErPATD1TEPwjX0CpZLWkzgaXdALjEcP_ohmiJzkw" </code></pre> <ul></ul><p>ls コマンドを実行すると、Drupal\Core\DependencyInjection\Container 型の $container という変数が利用できることがわかります。</p> <pre> <code>&gt;&gt;&gt; ls -l Variables: $container Drupal\Core\DependencyInjection\Container {#768 …604} &gt;&gt;&gt; </code></pre> <p>このオブジェクトの get() メソッドを利用して <a href="https://api.drupal.org/api/drupal/core%21core.services.yml/9.3.x" rel=" noopener" target="_blank">Drupal のコアサービス</a>のインスタンスを<a href="https://api.drupal.org/api/drupal/core%21core.api.php/group/container/9.3.x#sec_container" rel=" noopener" target="_blank">取得できます</a>。<a href="https://api.drupal.org/api/drupal/core%21core.services.yml/service/entity_type.manager/9.3.x" rel=" noopener" target="_blank">entity_type.manager</a> サービスの例を示します。</p> <pre> <code>// entity_type.manager サービスを取得する例 &gt;&gt;&gt; $manager = $container-&gt;get('entity_type.manager'); =&gt; Drupal\Core\Entity\EntityTypeManager {#943 +"_serviceId": "entity_type.manager", } </code></pre> <p>取得したインスタンスの型の詳細は doc コマンドで閲覧できます。</p> <pre> <code>&gt;&gt;&gt; doc $manager class Drupal\Core\Entity\EntityTypeManager extends Drupal\Core\Plugin\DefaultPluginManager implements Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, Drupal\Component\Plugin\Discovery\DiscoveryInterface, Drupal\Component\Plugin\Factory\FactoryInterface, Drupal\Component\Plugin\Mapper\MapperInterface, Drupal\Component\Plugin\PluginManagerInterface, Drupal\Core\Cache\CacheableDependencyInterface, Drupal\Core\Entity\EntityTypeManagerInterface, Symfony\Component\DependencyInjection\ContainerAwareInterface Description: Manages entity type plugin definitions. Each entity type definition array is set in the entity type's annotation and altered by hook_entity_type_alter(). Do not use hook_entity_type_alter() hook to add information to entity types, unless one of the following is true: - You are filling in default values. - You need to dynamically add information only in certain circumstances. - Your hook needs to run after hook_entity_type_build() implementations. Use hook_entity_type_build() instead in all other cases. See: \Drupal\Core\Entity\Annotation\EntityType See: \Drupal\Core\Entity\EntityInterface See: \Drupal\Core\Entity\EntityTypeInterface See: hook_entity_type_alter() See: hook_entity_type_build() </code></pre> <p>ls コマンドを使用すると、クラスのメンバを調べることができます。</p> <pre> <code>&gt;&gt;&gt; ls -al $manager; Class Properties: $additionalAnnotationNamespaces [] &gt; $alterHook "entity_type" &gt; $cacheBackend Drupal\Core\Cache\ChainedFastBackend &gt; $cacheKey "entity_type" &gt; $cacheTags [ …1] &gt; $classResolver Drupal\Core\DependencyInjection\Class&gt; $container Drupal\Core\DependencyInjection\Conta&gt; … Class Methods: alterDefinitions protected function alterDefinitions(&amp;$defini&gt; alterInfo protected function alterInfo($alter_hook) &gt; cacheGet protected function cacheGet($cid) &gt; cacheSet protected function cacheSet($cid, $data, $ex&gt; clearCachedDefinitions public function clearCachedDefinitions() &gt; createHandlerInstance public function createHandlerInstance($class&gt; createInstance public function createInstance($plugin_id, a&gt; doGetDefinition protected function doGetDefinition(array $de&gt; extractProviderFromDefinition protected function extractProviderFromDefini&gt; findDefinitions protected function findDefinitions() &gt; getAccessControlHandler public function getAccessControlHandler($ent&gt; getActiveDefinition public function getActiveDefinition($entity_&gt; getCacheContexts public function getCacheContexts() &gt; getCachedDefinitions protected function getCachedDefinitions() &gt; getCacheMaxAge public function getCacheMaxAge() &gt; getCacheTags public function getCacheTags() &gt; getDefinition public function getDefinition($entity_type_i&gt; getDefinitions public function getDefinitions() &gt; getDiscovery protected function getDiscovery() &gt; getFactory protected function getFactory() &gt; getFormObject public function getFormObject($entity_type_i&gt; getHandler public function getHandler($entity_type_id, &gt; getInstance public function getInstance(array $options) &gt; getListBuilder public function getListBuilder($entity_type_&gt; getRouteProviders public function getRouteProviders($entity_ty&gt; getStorage public function getStorage($entity_type_id) &gt; … </code></pre> <p>doc コマンドは、メソッドの詳細の確認にも利用できます。</p> <pre> <code>&gt;&gt;&gt; doc $manager-&gt;getStorage class Drupal\Core\Entity\EntityTypeManager extends Drupal\Core\Plugin\DefaultPluginManager implements Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, Drupal\Component\Plugin\Discovery\DiscoveryInterface, Drupal\Component\Plugin\Factory\FactoryInterface, Drupal\Component\Plugin\Mapper\MapperInterface, Drupal\Component\Plugin\PluginManagerInterface, Drupal\Core\Cache\CacheableDependencyInterface, Drupal\Core\Entity\EntityTypeManagerInterface, Symfony\Component\DependencyInjection\ContainerAwareInterface public function getStorage($entity_type_id) Description: {@inheritdoc} --- interface Drupal\Core\Entity\EntityTypeManagerInterface extends Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, Drupal\Component\Plugin\Discovery\DiscoveryInterface, Drupal\Component\Plugin\Factory\FactoryInterface, Drupal\Component\Plugin\Mapper\MapperInterface, Drupal\Component\Plugin\PluginManagerInterface abstract public function getStorage($entity_type_id) Description: Creates a new storage instance. Throws: \Drupal\Component\Plugin\Exception\PluginNotFoundException Thrown if the entity type doesn't exist. \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException Thrown if the storage handler couldn't be loaded. Param: string $entity_type_id The entity type ID for this storage. Return: \Drupal\Core\Entity\EntityStorageInterface A storage instance. </code></pre> <p>こうした情報を確認しながら、種々の API を呼び出していくことができます。</p> <pre> <code>// uid=1 のユーザーを読み込む例 &gt;&gt;&gt; $user = $manager-&gt;getStorage('user')-&gt;load(1); =&gt; Drupal\user\Entity\User {#5435 #uid: Drupal\Core\Field\FieldItemList {#5370}, #uuid: Drupal\Core\Field\FieldItemList {#5573}, #langcode: Drupal\Core\Field\FieldItemList {#5579}, #preferred_langcode: Drupal\Core\Field\FieldItemList {#5593}, #preferred_admin_langcode: Drupal\Core\Field\FieldItemList {#5607}, #name: Drupal\Core\Field\FieldItemList {#5609}, #pass: Drupal\Core\Field\FieldItemList {#5615}, #mail: Drupal\Core\Field\FieldItemList {#5625}, #timezone: Drupal\Core\Field\FieldItemList {#5631}, #status: Drupal\Core\Field\FieldItemList {#5637}, #created: Drupal\Core\Field\FieldItemList {#5643}, #changed: Drupal\Core\Field\ChangedFieldItemList {#5649}, #access: Drupal\Core\Field\FieldItemList {#5655}, #login: Drupal\Core\Field\FieldItemList {#5661}, #init: Drupal\Core\Field\FieldItemList {#5667}, #roles: Drupal\Core\Field\EntityReferenceFieldItemList {#5673}, #default_langcode: Drupal\Core\Field\FieldItemList {#5685}, #user_picture: Drupal\file\Plugin\Field\FieldType\FileFieldItemList {#5690}, } // インスタンスの内容をダンプする例 &gt;&gt;&gt; dump -a $user; Drupal\user\Entity\User {#5435 #values: [ "uid" =&gt; [ "x-default" =&gt; "1", ], "uuid" =&gt; [ "x-default" =&gt; "fa02f5db-396d-48af-b8f4-256ab018db5d", ], "langcode" =&gt; [ "x-default" =&gt; "ja", ], "preferred_langcode" =&gt; [ "x-default" =&gt; "ja", ], "preferred_admin_langcode" =&gt; [ "x-default" =&gt; null, ], "name" =&gt; [ "x-default" =&gt; "admin", ], "pass" =&gt; [ "x-default" =&gt; "$S$Ee85r8WTkOUqZHp1oIxIlig5fOntFC8DrfyOoq.93TE9ezcWD9jO", ], "mail" =&gt; [ "x-default" =&gt; "admin@example.com", ], "timezone" =&gt; [ "x-default" =&gt; "Asia/Tokyo", ], "status" =&gt; [ "x-default" =&gt; "1", ], "created" =&gt; [ "x-default" =&gt; "1639116396", ], :</code></pre> <p>名前空間を指定すると、クラス等の指定を簡略化できます。</p> <pre> <code>&gt;&gt;&gt; namespace Drupal\user\Entity &gt;&gt;&gt; User::load(1) =&gt; Drupal\user\Entity\User {#5435 …</code></pre> <p>show コマンドを利用して、特定のメソッドのコードを閲覧することもできます。</p> <pre> <code>&gt;&gt;&gt; show User::load 481: /** 482: * {@inheritdoc} 483: */ 484: public static function load($id) { 485: $entity_type_repository = \Drupal::service('entity_type.repository'); 486: $entity_type_manager = \Drupal::entityTypeManager(); 487: $storage = $entity_type_manager-&gt;getStorage($entity_type_repository-&gt;&gt; 488: return $storage-&gt;load($id); 489: } </code></pre> <p>プロジェクトを常に PhpStorm 等の IDE で開いて xdebug している場合は必要ないかもしれませんが、ターミナルでサイトを調査する時などは便利に活用できそうです。</p> <h2>参考資料</h2> <ul><li><a href="https://psysh.org/" rel=" noopener" target="_blank">psysh</a></li> <li><a href="http://blog.damiankloip.net/2015/drush-php" rel=" noopener" target="_blank">Drepl</a></li> <li><a href="/drush-commands/php_cli">drush php:cli コマンド</a></li> <li><a href="https://github.com/bobthecow/psysh" rel=" noopener" target="_blank">bobthecow/psysh</a></li> <li><a href="https://github.com/bobthecow/psysh/wiki/Home" rel=" noopener" target="_blank">PsySH manual</a></li> <li><a href="https://blog.kazu69.net/2015/02/28/php-repl-psysh-was-so-convenient/" rel=" noopener" target="_blank">phpのREPL psysh が便利そうだった</a></li> <li><a href="https://www.sitepoint.com/interactive-php-debugging-psysh/" rel=" noopener" target="_blank">Interactive PHP Debugging with PsySH</a></li> </ul><hr /><p><strong>追記:</strong></p> <blockquote class="twitter-tweet"> <p dir="ltr" lang="ja" xml:lang="ja">PsySh のブレークポイントってこれかな。eval(\Psy\sh()) の先で \Psy\debug() が呼ばれて、そこで Shell のインスタンスの run() を呼ぶんですね。<a href="https://t.co/XF0ySUBaj9">https://t.co/XF0ySUBaj9</a></p> — Kenji Shirane (@bkenro) <a href="https://twitter.com/bkenro/status/1481630487961808896?ref_src=twsrc%5Etfw">January 13, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/5" hreflang="ja">Drupal9</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/30" hreflang="ja">Drush</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/62" hreflang="ja">REPL</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/63" hreflang="ja">psysh</a></div> </div> </div> Fri, 10 Dec 2021 15:16:36 +0000 shirane 169 at https://www.white-root.com Docker-compose で Drupal 9 サイトを立ち上げる手順の動画 https://www.white-root.com/blog/1634836008 <span>Docker-compose で Drupal 9 サイトを立ち上げる手順の動画</span> <span><span>shirane</span></span> <span>2021/10/22(金) - 02:06</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>ffdsm(ふづむ)上の Docker で Drupal 9 をインストールする動画のその4です。</p> <p>前回までの設定とイメージを利用して Docker-compose で各コンテナを起動し、drush site:install コマンドで対話的に Drupal 9 をインストールする手順です。</p> <div class="youtube"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/27dY-NphVmE" title="YouTube video player" width="560"></iframe></div> <p>イメージの取得・ビルドからコンテナの起動までが自動化されるので、文字通りコマンド一発で Drupal 9 サイトのインストール準備が整います。</p> <ul><li>前回の動画<br /><a href="https://youtu.be/DuI-x9vygy8" target="_blank">https://youtu.be/DuI-x9vygy8</a></li> <li>ffdsm(ふづむ)<br /><a href="https://github.com/bkenro/ffdsm" target="_blank">https://github.com/bkenro/ffdsm</a></li> </ul></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/5" hreflang="ja">Drupal9</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/60" hreflang="ja">Docker</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/61" hreflang="ja">ffdsm</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/30" hreflang="ja">Drush</a></div> </div> </div> Thu, 21 Oct 2021 17:06:48 +0000 shirane 168 at https://www.white-root.com Drupal の Docker 公式イメージに Drush を追加してコマンドラインから Drupal 9 をインストールする手順の動画 https://www.white-root.com/blog/1634214873 <span>Drupal の Docker 公式イメージに Drush を追加してコマンドラインから Drupal 9 をインストールする手順の動画</span> <span><span>shirane</span></span> <span>2021/10/14(木) - 21:34</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>ffdsm(ふづむ)上の Docker で Drupal 9 をインストールする動画のその3です。</p> <p>今回は、Drupal の Docker 公式イメージに Drush と mysql コマンドを追加したイメージを作り、これを起動したコンテナに対して、コマンドラインから Drush で Drupal 9 サイトをインストールする手順を紹介しています。</p> <div class="youtube"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/DuI-x9vygy8" title="YouTube video player" width="560"></iframe></div> <p>できたサイトでは Drush が使えるので、CRON やキャッシュ再構築など種々のサイト管理操作をコマンドラインから行うことが可能です。ローカル作業の効率が向上します。</p> <ul><li>前回の動画<br /><a href="https://youtu.be/uetR-KWEkn4" target="_blank">https://youtu.be/uetR-KWEkn4</a></li> <li>ffdsm(ふづむ)<br /><a href="https://github.com/bkenro/ffdsm" target="_blank">https://github.com/bkenro/ffdsm</a></li> </ul></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/5" hreflang="ja">Drupal9</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/60" hreflang="ja">Docker</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/61" hreflang="ja">ffdsm</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/30" hreflang="ja">Drush</a></div> </div> </div> Thu, 14 Oct 2021 12:34:33 +0000 shirane 167 at https://www.white-root.com Drupal と MariaDB の Docker 公式イメージのコンテナを組み合わせて Drupal 9 サイトを立ち上げる手順の動画 https://www.white-root.com/blog/1633878881 <span>Drupal と MariaDB の Docker 公式イメージのコンテナを組み合わせて Drupal 9 サイトを立ち上げる手順の動画</span> <span><span>shirane</span></span> <span>2021/10/11(月) - 00:14</span> <div class="w3-section field field--name-body field--type-text-with-summary field--label-hidden w3-bar-item field__item"><p>先日の動画の続編として、ffdsm(ふづむ)上の Docker コンテナで Drupal 9 のローカルサイトを立ち上げる手順の動画を作成しました。今回は、Drupal とは別に MariaDB 公式イメージのコンテナも立ち上げて、両者を組み合わせてサイトを動かす例です。</p> <div class="youtube"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/uetR-KWEkn4" title="YouTube video player" width="560"></iframe></div> <p>2つのコンテナで共有するネットワークを作成した後、コンテナ起動時の環境変数で MariaDB のデータベースを作成し、これを Drupal コンテナ上の Drupal 9 サイトから接続して利用するまでの流れを(冗長ではありますが)ステップバイステップで示しています。</p> <p><b>関連 URL:</b></p> <ul><li>前回の動画<br /><a href="https://youtu.be/FSg94VK2QWY" target="_blank">https://youtu.be/FSg94VK2QWY</a></li> <li>ffdsm(ふづむ)<br /><a href="https://github.com/bkenro/ffdsm" target="_blank">https://github.com/bkenro/ffdsm</a></li> </ul></div> <div class="w3-section field field--name-field-tag field--type-entity-reference field--label-inline clearfix"> <label class="field__label">Tag</label> <div class="field__items"> <div class="w3-bar-item field__item"><a href="/taxonomy/term/5" hreflang="ja">Drupal9</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/60" hreflang="ja">Docker</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/61" hreflang="ja">ffdsm</a></div> <div class="w3-bar-item field__item"><a href="/taxonomy/term/58" hreflang="ja">MariaDB</a></div> </div> </div> Sun, 10 Oct 2021 15:14:41 +0000 shirane 166 at https://www.white-root.com