summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/content/posts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2023-09-07 22:27:48 +0900
committernsfisis <nsfisis@gmail.com>2023-09-07 22:35:53 +0900
commit994e0114d76ae19768d5c303874a968cf6369fd0 (patch)
tree5fd3f8b169eea00084b24fbae820f75273864d2a /vhosts/blog/content/posts
parent57f015992f678bfd7281f171fb9d71349c96a1a0 (diff)
downloadnsfisis.dev-994e0114d76ae19768d5c303874a968cf6369fd0.tar.gz
nsfisis.dev-994e0114d76ae19768d5c303874a968cf6369fd0.tar.zst
nsfisis.dev-994e0114d76ae19768d5c303874a968cf6369fd0.zip
meta: migrate to monorepo
Diffstat (limited to 'vhosts/blog/content/posts')
-rw-r--r--vhosts/blog/content/posts/2021-03-05/my-first-post.xml27
-rw-r--r--vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml778
-rw-r--r--vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml143
-rw-r--r--vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml87
-rw-r--r--vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml122
-rw-r--r--vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml270
-rw-r--r--vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml237
-rw-r--r--vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml165
-rw-r--r--vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml215
-rw-r--r--vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml563
-rw-r--r--vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml125
-rw-r--r--vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml171
-rw-r--r--vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml122
-rw-r--r--vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml72
-rw-r--r--vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml708
-rw-r--r--vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml188
-rw-r--r--vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml313
-rw-r--r--vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml174
-rw-r--r--vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml329
-rw-r--r--vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml97
-rw-r--r--vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml607
-rw-r--r--vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml187
-rw-r--r--vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml109
23 files changed, 5809 insertions, 0 deletions
diff --git a/vhosts/blog/content/posts/2021-03-05/my-first-post.xml b/vhosts/blog/content/posts/2021-03-05/my-first-post.xml
new file mode 100644
index 00000000..92732269
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-03-05/my-first-post.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>My First Post</title>
+ <abstract>
+ これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。
+ </abstract>
+ <revhistory>
+ <revision>
+ <date>2021-03-05</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="test">
+ <title>Test</title>
+ <para>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
+ velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+ occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+ mollit anim id est laborum.
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml
new file mode 100644
index 00000000..f8813273
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml
@@ -0,0 +1,778 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2021</title>
+ <abstract>
+ 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。
+ </abstract>
+ <keywordset>
+ <keyword>conference</keyword>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-03-30</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="report">
+ <title>PHPerKaigi 2021 参加レポ</title>
+ <para>
+ 2021-03-26 から 2021-03-28
+ にかけて開催された、 <link xl:href="https://phperkaigi.jp/2021/">PHPerKaigi 2021</link>
+ に一般参加者として参加した。
+ 弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link>
+ (今年1月から勤務)
+ はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。
+ </para>
+ <para>
+ このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。
+ </para>
+ <para>
+ 発表はトラック A、B に分かれていたのだが、今回はすべて A
+ トラックを視聴している (切り替えるのが面倒だっただけ)。
+ </para>
+ <section xml:id="report-legend">
+ <title>凡例</title>
+ <blockquote>
+ <para>
+ 発表・スライドのメモ (引用ではない)
+ </para>
+ </blockquote>
+ <para>
+ 感想など
+ </para>
+ </section>
+ <section xml:id="report--day-0">
+ <title>Day 0 前夜祭 (2021/03/27)</title>
+ <section xml:id="report--day-0--1730-a">
+ <title>17:30 [A]</title>
+ <para>
+ PHP で AWS Lambda
+ </para>
+ <blockquote>
+ <para>
+ Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる
+ PHP にリプレースを検討
+ </para>
+ <itemizedlist>
+ <listitem>サーバレス</listitem>
+ <listitem>サーバ・インフラの管理が不要</listitem>
+ <listitem>アプリケーションコードの知識だけで保守可能</listitem>
+ </itemizedlist>
+ <para>
+ ゼロベースで作れる案件が (Railsの件とは別に)
+ あるため、そちらで試験的に導入?
+ </para>
+ <para>
+ AWSの学習 AWS のドキュメント DevelopersIO
+ </para>
+ <para>
+ AWS Lambda のカスタムランタイムで PHP を動かす
+ </para>
+ <para>
+ サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP
+ を動かすツールがすでにある サーバーレス構築はすんなり
+ </para>
+ <para>
+ 今は Laravel がルーティングしている Laravel Livewire を Lambda
+ に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は
+ 15分の制限)
+ </para>
+ <para>
+ Lambda でコンテナイメージがサポートされるように
+ </para>
+ <para>
+ 抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる
+ </para>
+ </blockquote>
+ <para>
+ AWS Lambda のような Function as a Service
+ はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に
+ web サービスを作る具体的なイメージがまだ見えない (注: すべて for me
+ として書いている)。
+ </para>
+ <para>
+ PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。
+ </para>
+ <para>
+ 勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP +
+ Laravel などでは動かなさそう)
+ だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。
+ </para>
+ </section>
+ <section xml:id="report--day-0--1810-a">
+ <title>18:10 [A]</title>
+ <para>
+ 大規模サイトの SEO
+ </para>
+ <blockquote>
+ <para>
+ 大規模サイト (100万ページ以上) Google の基準
+ </para>
+ <para>
+ クロールバジェットを意識したSEO
+ </para>
+ <para>
+ 大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト
+ (10,000以上) でコンテンツが目まぐるしく変更される
+ これを満たさないなら、クロールバジェットを考えなくてもいい
+ </para>
+ <para>
+ サーチコンソール 「カバレッジ」の「除外」
+ 多すぎるのは問題→クロールバジェットを浪費している
+ </para>
+ <itemizedlist>
+ <listitem>クエリの順番を決める</listitem>
+ <listitem>空の値のルールを決めておく</listitem>
+ <listitem>リダイレクトすればインデックスはうまくいく</listitem>
+ <listitem>リンクが存在する限りクロールはされる</listitem>
+ </itemizedlist>
+ <para>
+ リニューアル前のURL
+ </para>
+ <para>
+ インデックスは移行される
+ リンクのURLが存在する限り、別のURLとしてクロールされる
+ リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い
+ リニューアルで無視されるようになったパラメータも注意
+ </para>
+ <para>
+ robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や
+ 301 を読ませる 内部リンクを確認する JS でのリンクに書き換え
+ </para>
+ <para>
+ クエリパラメータからURLのパスに <literal>/tokyo?area=HOGE</literal> → <literal>/tokyo/HOGE</literal>
+ </para>
+ <para>
+ URL 設計だいじ
+ </para>
+ </blockquote>
+ <para>
+ SEO (Search Engine Optimization)
+ は大して知らないので新鮮な話が多かった。その分語れることも少ない……。
+ </para>
+ </section>
+ <section xml:id="report--day-0--1850-a">
+ <title>18:50 [A]</title>
+ <blockquote>
+ <para>
+ 知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く
+ (閉じタグ・入れ子構造など)
+ </para>
+ <itemizedlist>
+ <listitem>標準の HTML を適切に使う</listitem>
+ <listitem>WAI-ARIA</listitem>
+ <listitem>キーボードフレンドリー</listitem>
+ <listitem>マシンフレンドリー</listitem>
+ <listitem>SEOフレンドリー</listitem>
+ </itemizedlist>
+ <para>
+ button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ
+ </para>
+ <para>
+ WAI-ARIA HTML では表現できないセマンティクスを追加する
+ </para>
+ <itemizedlist>
+ <listitem>
+ ロール
+ <itemizedlist>
+ <listitem>何をするのか?</listitem>
+ <listitem>ユーザーアクションによって変化しない</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ プロパティ
+ <itemizedlist>
+ <listitem>関連づけられたデータ</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ ステート
+ <itemizedlist>
+ <listitem>現在の状態</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA
+ を使えばいいというものではない
+ </para>
+ <para>
+ マウスホバーでツールチップが出てくるが、キーボード操作では出てこない
+ </para>
+ <para>
+ VoiceOver
+ </para>
+ <para>
+ 全ての属性を使う必要はない
+ あくまでアクセシビリティを上げるための方法の一つにすぎない
+ </para>
+ </blockquote>
+ <para>
+ つい最近 WAI-ARIA
+ についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが)
+ いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。
+ </para>
+ </section>
+ <section xml:id="report--day-0--1930-a">
+ <title>19:30 [A]</title>
+ <para>
+ PHP で FUSE
+ </para>
+ <para>
+ 個人的に楽しみだった発表。
+ </para>
+ <blockquote>
+ <para>
+ VFS (virtual filesystem) vs 具体的なファイルシステム
+ </para>
+ <para>
+ 最適な実装方法は状況により異なる
+ </para>
+ <para>
+ アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS
+ </para>
+ <para>
+ カーネルのプログラムを作るのは難しい
+ * 権限がデカすぎる
+ * システム全体がクラッシュ
+ * セキュリティリスク
+ * 開発サイクルを回しづらい
+ * ネイティブコードにコンパイルされる言語である必要がある
+ </para>
+ <para>
+ Filesystem in USEr space (FUSE)
+ </para>
+ <itemizedlist>
+ <listitem>特定の C の関数を呼ぶことで filesystem が作れる</listitem>
+ <listitem>FFI を持つ言語なら FUSE が使える</listitem>
+ </itemizedlist>
+ <para>
+ SSHFS / s3fs / Docker Desktop
+ </para>
+ <para>
+ Linux 以外でも使える
+ </para>
+ <itemizedlist>
+ <listitem>dokany (on Windows)</listitem>
+ <listitem>osxfuse</listitem>
+ </itemizedlist>
+ <para>
+ VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE:
+ カーネル空間からユーザ空間へ通信
+ </para>
+ <para>
+ 高レベルなラッパで型をつける
+ </para>
+ <para>
+ PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など)
+ </para>
+ <itemizedlist>
+ <listitem>grep できる</listitem>
+ <listitem>sed できる</listitem>
+ <listitem>編集できる</listitem>
+ </itemizedlist>
+ </blockquote>
+ <para>
+ 期待通りの興味深い発表だった。FUSE
+ 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。
+ この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ
+ (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。
+ </para>
+ </section>
+ </section>
+ <section xml:id="report--day-1">
+ <title>Day 1 (2021/03/27)</title>
+ <section xml:id="report--day-1--1050-a">
+ <title>10:50 [A]</title>
+ <para>
+ ATDD
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>ユーザーストーリー</listitem>
+ <listitem>ユニットテスト</listitem>
+ <listitem>CI/CD</listitem>
+ </itemizedlist>
+ <para>
+ ユーザストーリーの受け入れ条件が曖昧になりがち
+ デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている
+ </para>
+ <para>
+ Q2の強化 アジャイルテストの4象限
+ </para>
+ <para>
+ 技術面/ビジネス面
+ 開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後)
+ </para>
+ <itemizedlist>
+ <listitem>
+ Q1: 技術面 &amp; チーム支援
+ <itemizedlist>
+ <listitem>TDD</listitem>
+ <listitem>ユニットテストなど</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ Q2: ビジネス面 &amp; チーム支援
+ <itemizedlist>
+ <listitem>ATDD</listitem>
+ <listitem>ビジネス面の受け入れテストで駆動する</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ Agile Alliance ユーザストーリーのスキルレベルを高める
+ </para>
+ <para>
+ テストピラミッド
+ </para>
+ <itemizedlist>
+ <listitem>UI Tests</listitem>
+ <listitem>Service Tests</listitem>
+ <listitem>Unit Tests</listitem>
+ <listitem>異なる粒度のテストを書く</listitem>
+ <listitem>
+ 高レベルになるほど、持つべきテストは少なくなる
+ <itemizedlist>
+ <listitem>ピラミッド型になる</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ 高レベルテストが多すぎる→アイスクリームコーン アンチパターン
+ </para>
+ <para>
+ ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test
+ </para>
+ <para>
+ ストーリ受け入れテスト
+ </para>
+ <para>
+ 入れ子のフィードバックループ ATDD(外側) と TDD(内側)
+ </para>
+ <para>
+ 外部品質・内部品質
+ </para>
+ <para>
+ バーティカルスライスのデリバリー
+ </para>
+ <itemizedlist>
+ <listitem>cucumber</listitem>
+ <listitem>gauge</listitem>
+ <listitem>behat</listitem>
+ </itemizedlist>
+ <para>
+ ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い
+ 失敗することがわかっているテスト(レッドテスト)はCIから外す
+ 失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット
+ </para>
+ <para>
+ Continuous Testing
+ </para>
+ </blockquote>
+ <para>
+ User Acceptance Test (UAT)
+ くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。
+ 高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1150-a">
+ <title>11:50 [A]</title>
+ <para>
+ 型解析を用いたリファクタリング
+ </para>
+ <para>
+ 型のある世界で生きてきた身として大いに楽しみにしていた発表。
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>PHPStan</listitem>
+ <listitem>Phan</listitem>
+ <listitem>Psalm</listitem>
+ </itemizedlist>
+ <para>
+ autoload も認識できる bootstrapFiles
+ </para>
+ <para>
+ 編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく
+ 警告が多すぎると見落としてしまう・無視されやすくなる
+ </para>
+ <para>
+ 型がついていないことによるエラーが多い
+ </para>
+ <para>
+ 型よりも詳細な検査 <literal>Util_Assert::min</literal>
+ </para>
+ <para>
+ SQL を静的解析 placeholder の型付け
+ </para>
+ <para>
+ 警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan
+ の拡張を追加する
+ </para>
+ </blockquote>
+ <para>
+ 昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。
+ 今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で
+ Ruby の typeprof には注目している。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1230-a">
+ <title>12:30 [A]</title>
+ <para>
+ 昼食をとっていた。事前に何か食料を買っておくべきだった。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1310-a">
+ <title>13:10 [A]</title>
+ <para>
+ Documentation as Code
+ </para>
+ <para>
+ この発表も以前から非常に楽しみにしていた。
+ </para>
+ <blockquote>
+ <para>
+ 開発開始までのオーバーヘッド 新規にチームにジョイン
+ 担当範囲外の機能を理解 オンボーディングのコスト
+ </para>
+ <para>
+ PHPerKaigi 2020 で発表あり
+ </para>
+ <para>
+ 継続的にシステムの理解を助けるドキュメント
+ </para>
+ <para>
+ 継続的ドキュメンテーション システムとドキュメントの乖離
+ </para>
+ <para>
+ 書いてあることが間違っている・足りない * 徐々にずれていく *
+ システムの更新タイミングとドキュメントの更新タイミングに差がある
+ </para>
+ <para>
+ システムとドキュメントは対応関係がある * 間違ったドキュメント *
+ 存在しないドキュメント
+ </para>
+ <para>
+ システムとドキュメントの乖離を定量化する 継続的に
+ システムの更新に近いタイミングで ドキュメントを更新し続ける
+ </para>
+ <para>
+ Documentation as Code
+ </para>
+ <para>
+ コードと同じツールでドキュメントを書く * issue tracker * vcs * plain
+ text markup * automation
+ </para>
+ <para>
+ 開発者 システム ドキュメント 構造化データ ソフトウェア
+ </para>
+ <para>
+ システムから構造化データを抽出する PHPDoc OpenAPI
+ </para>
+ <para>
+ ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する
+ </para>
+ <para>
+ ビューの単位でドキュメントに
+ </para>
+ <para>
+ スタックトレースからのドキュメント生成
+ </para>
+ </blockquote>
+ <para>
+ ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な
+ (乖離しない)
+ 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1410-a">
+ <title>14:10 [A]</title>
+ <para>
+ cookie による session 管理
+ </para>
+ <para>
+ 全体的に基本的な話だったので特に触れない。Cookie
+ やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1450-a">
+ <title>14:50 [A]</title>
+ <para>
+ PHP のエラーと例外
+ </para>
+ <blockquote>
+ <para>
+ エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる
+ </para>
+ <para>
+ PHP7-8とエラー
+ </para>
+ <para>
+ PHPエンジンのエラーの一部が に変換されるようになった → try-catch
+ で捕捉できる
+ </para>
+ <para>
+ は例外とは異なる
+ </para>
+ <para>
+ PHP8 でエラーレベルの引き上げ
+ </para>
+ <itemizedlist>
+ <listitem>
+ 捕捉すべきもの
+ <itemizedlist>
+ <listitem>recoverable</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ 捕捉すべきでないもの
+ <itemizedlist>
+ <listitem>unrecoverable</listitem>
+ <listitem>開発時に対処できるもの</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ 例外 * 捕捉して事後処理 * 捕捉せず(or 捕捉した上で)さらに上に是非を問う
+ </para>
+ <para>
+ 開発段階で例外を把握し、ハンドリングを考えておく
+ </para>
+ <para>
+ と
+ </para>
+ <para>
+ はキャッチすべきでない
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+
+ </para>
+ <itemizedlist>
+ <listitem>本番で起きてはいけない</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <para>
+
+ </para>
+ <itemizedlist>
+ <listitem>本番で起きてはいけない →生じないのだから捕捉もしない</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <para>
+
+ </para>
+ <itemizedlist>
+ <listitem>起こるかもしれないので本番環境でも考慮する</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ 捕捉して対応するのではなく、未然に防ぐ
+ </para>
+ <para>
+ 独自例外を使う を投げてしまうと、 catch ()せざるを得ない →catch
+ 範囲が広すぎる
+ </para>
+ <para>
+ SPL の例外を使う
+ </para>
+ <para>
+ 例外翻訳
+ 上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する
+ 下位レイヤの知識に依存させない
+ </para>
+ <para>
+ @throws 捕捉してほしい例外を書き連ねておく
+ </para>
+ <para>
+ 呼び出しもとに負わせたい責任
+ </para>
+ </blockquote>
+ <para>
+ PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で
+ PHP を書き始めてから 4ヶ月ほどになる)。
+ </para>
+ <para>
+ 個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell
+ などのエラーを「値として」扱う言語だと思っている。try-catch
+ は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は
+ C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる
+ (C のそれはまともな型付けではない。念のため)。
+ </para>
+ <para>
+ PHP
+ のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1530-a">
+ <title>15:30 [A]</title>
+ <para>
+ Laravel のメール認証
+ </para>
+ <para>
+ Laravel
+ の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1610-a">
+ <title>16:10 [A]</title>
+ <para>
+ gRPC
+ </para>
+ <blockquote>
+ <para>
+ Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional
+ streaming RPCs
+ </para>
+ <para>
+ Protobuf
+ </para>
+ <para>
+ 実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える
+ </para>
+ <para>
+ マイクロサービスのサービス通信 スマホアプリ ゲームサーバ
+ </para>
+ <para>
+ PHP では?
+ </para>
+ <para>
+ PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て
+ </para>
+ <para>
+ PHP ではgRPCのクライアントしか対応していない
+ </para>
+ <para>
+ gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル
+ </para>
+ <para>
+ HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ
+ </para>
+ <para>
+ Envoy Nginx などで相互に gRPC と gRPC-Web で変換
+ </para>
+ <para>
+ Amp イベント駆動な並行処理のフレームワーク
+ </para>
+ <para>
+ HTTP/2 対応
+ </para>
+ <para>
+ C#のgRPC-Webが楽
+ </para>
+ </blockquote>
+ <para>
+ (発表の中でもまさに同じことをおっしゃっていたが) PHP
+ 以外の方が向いているだろう、というのが第一の感想である。gRPC
+ はそれ自体というよりも Protobuf
+ というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。
+ </para>
+ </section>
+ <section xml:id="report--day-1--1650-a">
+ <title>16:50 [A]</title>
+ <para>
+ アーキテクチャテスト
+ </para>
+ <blockquote>
+ <para>
+ Independent Core Layer Pattern
+ </para>
+ <para>
+ 開発初期のアーキテクチャが崩れる
+ アーキテクチャ観点のコードレビューができない
+ </para>
+ <para>
+ どこにクラスを置けばよいか? ドキュメントがない
+ </para>
+ <para>
+ アーキテクチャ設計に関する知識が属人化・暗黙知化
+ </para>
+ <para>
+ ガイドライン * 最初にルールを決めるのは簡単 *
+ ルール通り作り始めるのも簡単 *
+ →維持するのが難しい、人が決めたものゆえ壊れやすい
+ </para>
+ <para>
+ PHP の特性 * クラスは public * 可視性の制御が public / protected /
+ private のみ * 依存関係の制御が困難
+ </para>
+ <para>
+ アーキテクチャテスト
+ クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する
+ </para>
+ <itemizedlist>
+ <listitem>deptrac</listitem>
+ <listitem>phpat</listitem>
+ </itemizedlist>
+ <para>
+ Independent Core Layer Pattern
+ </para>
+ <para>
+ アーキテクチャテストの失敗 * 実装誤り * or アーキテクチャが適切でない *
+ 開発の過程でフィードバックしていく
+ </para>
+ <para>
+ モジュラーモノリス→マイクロサービスへ
+ </para>
+ </blockquote>
+ </section>
+ </section>
+ <section xml:id="report--day-2">
+ <title>Day 2 (2021/03/28)</title>
+ <para>
+ 冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。
+ </para>
+ <para>
+ 残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。
+ </para>
+ </section>
+ <section xml:id="report--comments">
+ <title>全体の感想</title>
+ <para>
+ Day 2
+ にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも
+ (特に初参加者として) 嬉しいポイントだった。
+ </para>
+ <para>
+ 今回、雑談/登壇者への質問等向けに Discord
+ サーバもあったのだが、こちらは参加こそしたものの ROM
+ のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord
+ 表示に
+ 1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった
+ (さらにいうと Zoom
+ でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。
+ </para>
+ <para>
+ 1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord
+ しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。
+ まあ初カンファレンスだし、とお茶を濁しておこう。
+ </para>
+ <para>
+ さて、カンファレンスで一つ気になったことがある。それは、Discord
+ という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord
+ の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord
+ があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。
+ </para>
+ <para>
+ <hr/>
+ </para>
+ <para>
+ 最後になりましたが、毎年の PHPerKaigi
+ 開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました!
+ (ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い)
+ </para>
+ <para>
+ ではまた来年。
+ </para>
+ </section>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml
new file mode 100644
index 00000000..f87d3a65
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【C++】 属性構文の属性名にはキーワードが使える</title>
+ <abstract>
+ C++ の属性構文の属性名には、キーワードが使える。ネタ記事。
+ </abstract>
+ <keywordset>
+ <keyword>cpp</keyword>
+ <keyword>cpp17</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/94090937bcf860cfa93b">https://qiita.com/nsfisis/items/94090937bcf860cfa93b</link>
+ </note>
+ <para>
+ タイトル落ち。まずはこのコードを見て欲しい。
+ </para>
+ <programlisting language="cpp" linenumbering="unnumbered">
+ <![CDATA[
+ #include <iostream>
+
+ [[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
+ [[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
+ [[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
+ [[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
+ [[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
+ [[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
+ [[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
+ [[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
+ [[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
+ [[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
+ [[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
+ [[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
+ [[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
+ // [[using]]
+ int main() {
+ std::cout << "Hello, World!" << std::endl;
+ }
+ ]]>
+ </programlisting>
+ <blockquote>
+ <para>
+ コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0
+ (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model:
+ posix InstalledDir:
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
+ </para>
+ <para>
+ コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp
+ </para>
+ </blockquote>
+ <para>
+ この記事から得られるものはこれ以上ないので以下は蛇足になる。
+ </para>
+ <para>
+ 別件で cppreference.com の
+ <link xl:href="https://en.cppreference.com/w/cpp/language/identifiers">identifier
+ のページ</link> を読んでいた時、次の文が目に止まった。
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>
+ the identifiers that are keywords cannot be used for other purposes;
+ <itemizedlist>
+ <listitem>
+ The only place they can be used as non-keywords is in an
+ attribute-token. (e.g. [[private]] is a valid attribute) (since C++11)
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ </blockquote>
+ <para>
+ キーワードでも属性として指定する場合は非キーワードとして使えるらしい。
+ 実際にやってみる。
+ </para>
+ <para>
+ 同サイトの <link xl:href="https://en.cppreference.com/w/cpp/keyword">keywords のページ</link>
+ から一覧を拝借し、上のコードが出来上がった (C++17
+ においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown
+ attribute `〇〇' ignored)
+ がコンパイラから出力されるが、コンパイルできる。
+ </para>
+ <para>
+ 上のコードでは <literal>[[using]]</literal> をコメントアウトしているが、これは <literal>using</literal>
+ キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。
+ </para>
+ <programlisting language="cpp" linenumbering="unnumbered">
+ <![CDATA[
+ // using の例
+ [[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文
+ ]]>
+ </programlisting>
+ <para>
+ C++17 の仕様も見てみる (正確には標準化前のドラフト)。
+ </para>
+ <para>
+ 引用元: <link xl:href="https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4">https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4</link>
+ </para>
+ <blockquote>
+ <para>
+ If a keyword or an alternative token that satisfies the syntactic
+ requirements of an identifier is contained in an attribute-token, it is
+ considered an identifier.
+ </para>
+ </blockquote>
+ <para>
+ 「<literal>identifier</literal> の構文上の要件を満たすキーワードまたは代替トークンが
+ <literal>attribute-token</literal> に含まれている場合、<literal>identifier</literal>
+ とみなされる」とある。どうやら間違いないようだ。
+ </para>
+ <para>
+ ところで、代替トークン (alternative token) とは <literal>and</literal> (<literal>&amp;</literal>) や <literal>bitor</literal>
+ (<literal>|</literal>) などのことだが、<literal>identifier</literal>
+ の構文上の要件を満たさないような代替トークンなどあるのか?
+ 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい
+ (参考:
+ <link xl:href="https://timsong-cpp.github.io/cppwp/n4659/lex.digraph">同ドラフト</link>)
+ </para>
+ <itemizedlist>
+ <listitem><literal>&lt;%</literal> → <literal>{</literal></listitem>
+ <listitem><literal>%&gt;</literal> → <literal>}</literal></listitem>
+ <listitem><literal>&lt;:</literal> → <literal>[</literal></listitem>
+ <listitem><literal>:&gt;</literal> → <literal>]</literal></listitem>
+ <listitem><literal>%:</literal> → <literal>#</literal></listitem>
+ <listitem><literal>%:%:</literal> → <literal>##</literal></listitem>
+ </itemizedlist>
+ <para>
+ 「<literal>identifier</literal>
+ の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。
+ </para>
+ <para>
+ 調べた感想: 字句解析器か構文解析器が辛そう
+ </para>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml
new file mode 100644
index 00000000..eb8aa452
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</title>
+ <abstract>
+ Python における UnboundLocalError の理由と対処法。
+ </abstract>
+ <keywordset>
+ <keyword>python</keyword>
+ <keyword>python3</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/5d733703afcb35bbf399">https://qiita.com/nsfisis/items/5d733703afcb35bbf399</link>
+ </note>
+ <para>
+ 本記事は Python 3.7.6 の動作結果を元にして書かれている。
+ </para>
+ <para>
+ Python でクロージャを作ろうと、次のようなコードを書いた。
+ </para>
+ <programlisting language="python" linenumbering="unnumbered">
+ <![CDATA[
+ def f():
+ x = 0
+ def g():
+ x += 1
+ g()
+
+ f()
+ ]]>
+ </programlisting>
+ <para>
+ 関数 <literal>g</literal> から 関数 <literal>f</literal> のスコープ内で定義された変数 <literal>x</literal> を参照し、それに
+ 1 を足そうとしている。 これを実行すると <literal>x += 1</literal>
+ の箇所でエラーが発生する。
+ </para>
+ <blockquote>
+ <para>
+ UnboundLocalError: local variable `x' referenced before assignment
+ </para>
+ </blockquote>
+ <para>
+ local変数 <literal>x</literal> が代入前に参照された、とある。これは、<literal>f</literal> の <literal>x</literal>
+ を参照するのではなく、新しく別の変数を <literal>g</literal> 内に作ってしまっているため。
+ 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。<literal>var</literal>
+ を変数宣言のための構文として擬似的に利用している。
+ </para>
+ <programlisting language="python" linenumbering="unnumbered">
+ <![CDATA[
+ # 注: var は正しい Python の文法ではない。上記参照のこと
+ def f():
+ var x # f の local変数 'x' を宣言
+ x = 0 # x に 0 を代入
+ def g(): # f の内部関数 g を定義
+ var x # g の local変数 'x' を宣言
+ # たまたま f にも同じ名前の変数があるが、それとは別の変数
+ x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文)
+ # 加算する前の値を参照しようとするが、まだ代入されていないためエラー
+ g()
+ ]]>
+ </programlisting>
+ <para>
+ 当初の意図を表現するには、次のように書けばよい。
+ </para>
+ <programlisting language="python" linenumbering="unnumbered">
+ <![CDATA[
+ def f():
+ x = 0
+ def g():
+ nonlocal x ## (*)
+ x += 1
+ g()
+ ]]>
+ </programlisting>
+ <para>
+ <literal>(*)</literal> のように、<literal>nonlocal</literal> を追加する。これにより一つ外側のスコープ (<literal>g</literal>
+ の一つ外側 = <literal>f</literal>) で定義されている <literal>x</literal> を探しに行くようになる。
+ </para>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml
new file mode 100644
index 00000000..7c0c960d
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【Ruby】 自身を実行している処理系の種類を判定する</title>
+ <abstract>
+ Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。
+ </abstract>
+ <keywordset>
+ <keyword>ruby</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791">https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791</link>
+ </note>
+ <para>
+ Ruby
+ という言語には複数の実装があるが、それらをスクリプト上からどのようにして
+ programmatically に見分ければよいだろうか。
+ </para>
+ <para>
+ <literal>Object</literal> クラスに定義されている <literal>RUBY_ENGINE</literal>
+ という定数がこの用途に使える。
+ </para>
+ <para>
+ 参考:
+ <link xl:href="https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html">Object::RUBY_ENGINE</link>
+ </para>
+ <para>
+ 上記ページの例から引用する:
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ ruby-1.9.1 -ve 'p RUBY_ENGINE'
+ ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux]
+ "ruby"
+ $ jruby -ve 'p RUBY_ENGINE'
+ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]
+ "jruby"
+ ]]>
+ </programlisting>
+ <para>
+ それぞれの処理系がどのような値を返すかだが、stack overflow
+ に良い質問と回答があった。
+ </para>
+ <para>
+ <link xl:href="https://stackoverflow.com/a/9894232">What values for RUBY_ENGINE
+ correspond to which Ruby implementations?</link> より引用:
+ </para>
+ <blockquote>
+ <table>
+ <thead>
+ <tr>
+ <td>RUBY_ENGINE</td>
+ <td>Implementation</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>&lt;undefined&gt;</td>
+ <td>MRI &lt; 1.9</td>
+ </tr>
+ <tr>
+ <td>`ruby'</td>
+ <td>MRI &gt;= 1.9 or REE</td>
+ </tr>
+ <tr>
+ <td>`jruby'</td>
+ <td>JRuby</td>
+ </tr>
+ <tr>
+ <td>`macruby'</td>
+ <td>MacRuby</td>
+ </tr>
+ <tr>
+ <td>`rbx'</td>
+ <td>Rubinius</td>
+ </tr>
+ <tr>
+ <td>`maglev'</td>
+ <td>MagLev</td>
+ </tr>
+ <tr>
+ <td>`ironruby'</td>
+ <td>IronRuby</td>
+ </tr>
+ <tr>
+ <td>`cardinal'</td>
+ <td>Cardinal</td>
+ </tr>
+ </tbody>
+ </table>
+ </blockquote>
+ <para>
+ なお、この質問・回答は
+ 2014年になされたものであり、値は変わっている可能性がある。MRI (aka
+ CRuby) については執筆時現在 (2020/12/8) も <literal>'ruby'</literal>
+ が返ってくることを確認済み。
+ </para>
+ <para>
+ この表にない主要な処理系として、https://mruby.org[mruby] は <literal>'mruby'</literal>
+ を返す。
+ </para>
+ <para>
+ <link xl:href="https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35">mruby
+ 該当部分のソース</link> より引用:
+ </para>
+ <programlisting language="c" linenumbering="unnumbered">
+ <![CDATA[
+ /*
+ * Ruby engine.
+ */
+ #define MRUBY_RUBY_ENGINE "mruby"
+ ]]>
+ </programlisting>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml
new file mode 100644
index 00000000..0a799a3b
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【Ruby】 then キーワードと case in</title>
+ <abstract>
+ Ruby 3.0 で追加される case in 構文と、then キーワードについて。
+ </abstract>
+ <keywordset>
+ <keyword>ruby</keyword>
+ <keyword>ruby3</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/787a8cf888a304497223">https://qiita.com/nsfisis/items/787a8cf888a304497223</link>
+ </note>
+ <section xml:id="tl-dr">
+ <title>TL; DR</title>
+ <para>
+ <literal>case</literal> - <literal>in</literal> によるパターンマッチング構文でも、<literal>case</literal> - <literal>when</literal>
+ と同じように <literal>then</literal> が使える (場合によっては使う必要がある)。
+ </para>
+ </section>
+ <section xml:id="what-is-then-keyword">
+ <title><literal>then</literal> とは</title>
+ <para>
+ 使われることは稀だが、Ruby では <literal>then</literal>
+ がキーワードになっている。次のように使う:
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ if cond then
+ puts "Y"
+ else
+ puts "N"
+ end
+ ]]>
+ </programlisting>
+ <para>
+ このキーワードが現れうる場所はいくつかあり、<literal>if</literal>、<literal>unless</literal>、<literal>rescue</literal>、<literal>case</literal>
+ 構文がそれに当たる。 上記のように、何か条件を書いた後 <literal>then</literal>
+ を置き、式がそこで終了していることを示すマーカーとして機能する。
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ # Example:
+
+ if x then
+ a
+ end
+
+ unless x then
+ a
+ end
+
+ begin
+ a
+ rescue then
+ b
+ end
+
+ case x
+ when p then
+ a
+ end
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="why-then-is-usually-unnecessary">
+ <title>なぜ普段は書かなくてもよいのか</title>
+ <para>
+ 普通 Ruby のコードで <literal>then</literal>
+ を書くことはない。なぜか。次のコードを実行してみるとわかる。
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ if true puts 'Hello, World!' end
+ ]]>
+ </programlisting>
+ <para>
+ 次のような構文エラーが出力される。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ 20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n'
+ if true puts 'Hello, World!' end
+ ^~~~
+ 20:1: syntax error, unexpected `end', expecting end-of-input
+ ...f true puts 'Hello, World!' end
+ ]]>
+ </literallayout>
+ <para>
+ 二つ目のメッセージは無視して一つ目を読むと、<literal>then</literal> か <literal>;</literal>
+ か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。
+ </para>
+ <para>
+ ポイントは改行が <literal>then</literal> (や <literal>;</literal>) の代わりとなることである。<literal>true</literal>
+ の後に改行を入れてみる。
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ if true
+ puts 'Hello, World!' end
+ ]]>
+ </programlisting>
+ <para>
+ 無事 Hello, World! と出力されるようになった。
+ </para>
+ </section>
+ <section xml:id="why-then-or-linebreak-is-needed">
+ <title>なぜ <literal>then</literal> や <literal>;</literal> や改行が必要か</title>
+ <para>
+ なぜ <literal>then</literal> や <literal>;</literal> や改行 (以下 「<literal>then</literal> 等」)
+ が必要なのだろうか。次の例を見てほしい:
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ if a b end
+ ]]>
+ </programlisting>
+ <para>
+ <literal>then</literal> も <literal>;</literal>
+ も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。
+ この例は二通りに解釈できる。
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ # a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
+ if a then
+ b
+ end
+ ]]>
+ </programlisting>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ # a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
+ # その結果が truthy なら何もしない
+ if a(b) then
+ end
+ ]]>
+ </programlisting>
+ <para>
+ <literal>then</literal> 等はこの曖昧性を排除するためにあり、条件式は <literal>if</literal> から <literal>then</literal>
+ 等までの間にある、ということを明確にする。 C系の <literal>if</literal> 後に来る <literal>(</literal>/<literal>)</literal>
+ や、Python の <literal>:</literal>、Rust/Go/Swift などの <literal>{</literal> も同じ役割を持つ。
+ </para>
+ <para>
+ Ruby の場合、プログラマーが書きやすいよう改行でもって <literal>then</literal>
+ が代用できるので、ほとんどの場合 <literal>then</literal> は必要ない。
+ </para>
+ </section>
+ <section xml:id="then-in-case-in">
+ <title><literal>case</literal> - <literal>in</literal> における <literal>then</literal></title>
+ <para>
+ ようやく本題にたどり着いた。来る Ruby 3.0 では <literal>case</literal> と <literal>in</literal>
+ キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして
+ <literal>then</literal> 等が必要になる。 (現在の) Ruby には formal
+ な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc
+ の説明は省略)。
+ </para>
+ <para>
+ <link xl:href="https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986">https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986</link>
+ </para>
+ <programlisting language="yacc" linenumbering="unnumbered">
+ <![CDATA[
+ p_case_body : keyword_in
+ {
+ SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
+ p->command_start = FALSE;
+ $<ctxt>1 = p->ctxt;
+ p->ctxt.in_kwarg = 1;
+ $<tbl>$ = push_pvtbl(p);
+ }
+ {
+ $<tbl>$ = push_pktbl(p);
+ }
+ p_top_expr then
+ {
+ pop_pktbl(p, $<tbl>3);
+ pop_pvtbl(p, $<tbl>2);
+ p->ctxt.in_kwarg = $<ctxt>1.in_kwarg;
+ }
+ compstmt
+ p_cases
+ {
+ /*%%%*/
+ $$ = NEW_IN($4, $7, $8, &@$);
+ /*% %*/
+ /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
+ }
+ ;
+ ]]>
+ </programlisting>
+ <para>
+ 簡略版:
+ </para>
+ <programlisting language="yacc" linenumbering="unnumbered">
+ <![CDATA[
+ p_case_body : keyword_in p_top_expr then compstmt p_cases
+ ;
+ ]]>
+ </programlisting>
+ <para>
+ ここで、<literal>keyword_in</literal> は文字通り <literal>in</literal>、<literal>p_top_expr</literal>
+ はいわゆるパターン、<literal>then</literal> は <literal>then</literal>
+ キーワードのことではなく、この記事で <literal>then</literal> 等と呼んでいるもの、つまり
+ <literal>then</literal> キーワード、<literal>;</literal>、改行のいずれかである。
+ </para>
+ <para>
+ これにより、<literal>case</literal> - <literal>when</literal> による従来の構文と同じように、<literal>then</literal>
+ 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ case x
+ in 1 then a
+ in 2 then b
+ in 3 then c
+ end
+
+ case x
+ in 1
+ a
+ in 2
+ b
+ in 3
+ c
+ end
+
+ case x
+ in 1; a
+ in 2; b
+ in 3; c
+ end
+ ]]>
+ </programlisting>
+ <para>
+ ところで、<literal>p_top_expr</literal> には <literal>if</literal> による guard clause
+ が書けるので、その場合は <literal>if</literal> - <literal>then</literal> と似たような見た目になる。
+ </para>
+ <programlisting language="ruby" linenumbering="unnumbered">
+ <![CDATA[
+ case x
+ in 0 then a
+ in n if n < 0 then b
+ in n then c
+ end
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="outro">
+ <title>まとめ</title>
+ <itemizedlist>
+ <listitem>
+ <literal>if</literal> や <literal>case</literal> の条件の後ろには <literal>then</literal>、<literal>;</literal>、改行のいずれかが必要
+ <itemizedlist>
+ <listitem>通常は改行しておけばよい</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>3.0 で入る予定の <literal>case</literal> - <literal>in</literal> でも <literal>then</literal> 等が必要になる</listitem>
+ <listitem>Ruby の構文を正確に知るには (現状) <literal>parse.y</literal> を直接読めばよい</listitem>
+ </itemizedlist>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml
new file mode 100644
index 00000000..3aaca63f
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>Rust のプリミティブ型はどこからやって来るか</title>
+ <abstract>
+ Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。
+ </abstract>
+ <keywordset>
+ <keyword>rust</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/9a429432258bbcd6c565">https://qiita.com/nsfisis/items/9a429432258bbcd6c565</link>
+ </note>
+ <section xml:id="intro">
+ <title>前置き</title>
+ <para>
+ Rust
+ において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。
+ </para>
+ <programlisting language="rust" linenumbering="unnumbered">
+ <![CDATA[
+ #![allow(non_camel_case_types)]
+ #![allow(dead_code)]
+
+ struct bool;
+ struct char;
+ struct i8;
+ struct i16;
+ struct i32;
+ struct i64;
+ struct i128;
+ struct isize;
+ struct u8;
+ struct u16;
+ struct u32;
+ struct u64;
+ struct u128;
+ struct usize;
+ struct f32;
+ struct f64;
+ struct str;
+ ]]>
+ </programlisting>
+ <para>
+ では、普段単に <literal>bool</literal> と書いたとき、この <literal>bool</literal>
+ は一体どこから来ているのか。rustc のソースを追ってみた。
+ </para>
+ <blockquote>
+ <para>
+ 前提知識: 一般的なコンパイラの構造、用語。<literal>rustc</literal> そのものの知識は不要
+ (というよりも筆者自身がよく知らない)
+ </para>
+ </blockquote>
+ </section>
+ <section xml:id="code-reading">
+ <title>調査</title>
+ <para>
+ 調査に使用したソース (調査時点での最新 master)
+ </para>
+ <para>
+ <link xl:href="https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98">https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98</link>
+ </para>
+ <para>
+ どのようにして調べるか。rustc
+ の構造には詳しくないため、すぐに当たりをつけるのは難しい。
+ </para>
+ <para>
+ 大雑把な構造としては、<literal>compiler</literal> フォルダ以下に <literal>rustc_*</literal>
+ という名前のクレートが数十個入っている。これがどうやら <literal>rustc</literal>
+ コマンドの実装部のようだ。
+ </para>
+ <para>
+ <literal>rustc</literal> はセルフホストされている (= <literal>rustc</literal> 自身が Rust で書かれている)
+ ので、<literal>bool</literal> や <literal>char</literal>
+ などで適当に検索をかけてもノイズが多すぎて話にならない。
+ しかし、お誂え向きなことに <literal>i128</literal>/<literal>u128</literal>
+ というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って
+ <literal>git grep</literal> してみる。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ $ git grep "\bi128\b" | wc # i128
+ 165 1069 15790
+
+ $ git grep "\bu128\b" | wc # u128
+ 293 2127 26667
+
+ $ git grep "\bbool\b" | wc # cf. bool の結果
+ 3563 23577 294659
+ ]]>
+ </literallayout>
+ <para>
+ 165
+ 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ $ git grep "\bi128\b"
+ ...
+ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));
+ ...
+ ]]>
+ </literallayout>
+ <para>
+ <literal>rustc_resolve</literal>
+ というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。
+ </para>
+ <programlisting language="rust" linenumbering="unnumbered">
+ <![CDATA[
+ /// Interns the names of the primitive types.
+ ///
+ /// All other types are defined somewhere and possibly imported, but the primitive ones need
+ /// special handling, since they have no place of origin.
+ struct PrimitiveTypeTable {
+ primitive_types: FxHashMap<Symbol, PrimTy>,
+ }
+
+ impl PrimitiveTypeTable {
+ fn new() -> PrimitiveTypeTable {
+ let mut table = FxHashMap::default();
+
+ table.insert(sym::bool, Bool);
+ table.insert(sym::char, Char);
+ table.insert(sym::f32, Float(FloatTy::F32));
+ table.insert(sym::f64, Float(FloatTy::F64));
+ table.insert(sym::isize, Int(IntTy::Isize));
+ table.insert(sym::i8, Int(IntTy::I8));
+ table.insert(sym::i16, Int(IntTy::I16));
+ table.insert(sym::i32, Int(IntTy::I32));
+ table.insert(sym::i64, Int(IntTy::I64));
+ table.insert(sym::i128, Int(IntTy::I128));
+ table.insert(sym::str, Str);
+ table.insert(sym::usize, Uint(UintTy::Usize));
+ table.insert(sym::u8, Uint(UintTy::U8));
+ table.insert(sym::u16, Uint(UintTy::U16));
+ table.insert(sym::u32, Uint(UintTy::U32));
+ table.insert(sym::u64, Uint(UintTy::U64));
+ table.insert(sym::u128, Uint(UintTy::U128));
+ Self { primitive_types: table }
+ }
+ }
+ ]]>
+ </programlisting>
+ <para>
+ これは初めに列挙したプリミティブ型の一覧と一致している。doc comment
+ にも、
+ </para>
+ <blockquote>
+ <para>
+ All other types are defined somewhere and possibly imported, but the
+ primitive ones need special handling, since they have no place of
+ origin.
+ </para>
+ </blockquote>
+ <para>
+ とある。次はこの struct
+ の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
+ </para>
+ <programlisting language="rust" linenumbering="unnumbered">
+ <![CDATA[
+ /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
+ /// (略)
+ fn resolve_ident_in_lexical_scope(
+ &mut self,
+ mut ident: Ident,
+ ns: Namespace,
+ // (略)
+ ) -> Option<LexicalScopeBinding<'a>> {
+ // (略)
+
+ if ns == TypeNS {
+ if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) {
+ let binding =
+ (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root())
+ .to_name_binding(self.arenas);
+ return Some(LexicalScopeBinding::Item(binding));
+ }
+ }
+
+ None
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 関数名や doc comment が示している通り、この関数は識別子 (identifier,
+ ident) を現在のレキシカルスコープ内で解決 (resolve) する。
+ <literal>if ns == TypeNS</literal> のブロック内では、<literal>primitive_type_table</literal> (上記の
+ <literal>PrimitiveTypeTable::new()</literal> で作られた変数) に含まれている識別子
+ (<literal>bool</literal>、<literal>i32</literal> など)
+ かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。
+ </para>
+ <para>
+ なお、<literal>ns</literal> は「名前空間」を示す変数である。Rust
+ における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この
+ <literal>if</literal>
+ は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。
+ </para>
+ <para>
+ 重要なのは、これが <literal>resolve_ident_in_lexical_scope()</literal>
+ の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。
+ </para>
+ <para>
+ 動作がわかったところで、例として次のコードを考える。
+ </para>
+ <programlisting language="rust" linenumbering="unnumbered">
+ <![CDATA[
+ #![allow(non_camel_case_types)]
+
+ struct bool;
+
+ fn main() {
+ let _: bool = bool;
+ }
+ ]]>
+ </programlisting>
+ <para>
+ ここで <literal>main()</literal> の <literal>bool</literal> は <literal>struct bool</literal>
+ として解決される。なぜなら、プリミティブ型の判定をする前に <literal>bool</literal>
+ という名前の別の型が見つかるからだ。
+ </para>
+ </section>
+ <section xml:id="outro">
+ <title>まとめ</title>
+ <para>
+ Rust
+ のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml
new file mode 100644
index 00000000..ef17ae38
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【Vim】 autocmd events の BufWrite/BufWritePre の違い</title>
+ <abstract>
+ Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。
+ </abstract>
+ <keywordset>
+ <keyword>vim</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/79ab4db8564032de0b25">https://qiita.com/nsfisis/items/79ab4db8564032de0b25</link>
+ </note>
+ <section xml:id="tl-dr">
+ <title>TL; DR</title>
+ <para>
+ 違いはない。ただのエイリアス。
+ </para>
+ </section>
+ <section xml:id="code-reading">
+ <title>調査記録</title>
+ <para>
+ Vim の autocmd events には似通った名前のものがいくつかある。大抵は
+ <literal>:help</literal>
+ に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。
+ </para>
+ <itemizedlist>
+ <listitem><literal>BufRead</literal>/<literal>BufReadPost</literal></listitem>
+ <listitem><literal>BufWrite</literal>/<literal>BufWritePre</literal></listitem>
+ <listitem><literal>BufAdd</literal>/<literal>BufCreate</literal></listitem>
+ </itemizedlist>
+ <para>
+ このうち、<literal>BufAdd</literal>/<literal>BufCreate</literal> に関しては、<literal>:help BufCreate</literal> に
+ </para>
+ <blockquote>
+ <para>
+ The BufCreate event is for historic reasons.
+ </para>
+ </blockquote>
+ <para>
+ とあり、おそらくは <literal>BufAdd</literal>
+ のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため
+ vim と neovim のソースコードを調査した。
+ </para>
+ <blockquote>
+ <para>
+ ソースコードへのリンク
+ <link xl:href="https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2">vim (調査時点での master branch)</link>
+ <link xl:href="https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3">neovim (上に同じ)</link>
+ </para>
+ </blockquote>
+ <section xml:id="code-reading--vim">
+ <title>vim のソースコード</title>
+ <para>
+ 以下は、autocmd events
+ の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。
+ </para>
+ <para>
+ <link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86</link>
+ </para>
+ <programlisting language="c" linenumbering="unnumbered">
+ <![CDATA[
+ {"BufAdd", EVENT_BUFADD},
+ {"BufCreate", EVENT_BUFADD},
+ ]]>
+ </programlisting>
+ <para>
+ <link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97</link>
+ </para>
+ <programlisting language="c" linenumbering="unnumbered">
+ <![CDATA[
+ {"BufRead", EVENT_BUFREADPOST},
+ {"BufReadCmd", EVENT_BUFREADCMD},
+ {"BufReadPost", EVENT_BUFREADPOST},
+ ]]>
+ </programlisting>
+ <para>
+ <link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105</link>
+ </para>
+ <programlisting language="c" linenumbering="unnumbered">
+ <![CDATA[
+ {"BufWrite", EVENT_BUFWRITEPRE},
+ {"BufWritePost", EVENT_BUFWRITEPOST},
+ {"BufWritePre", EVENT_BUFWRITEPRE},
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="code-reading--neovim">
+ <title>neovim のソースコード</title>
+ <para>
+ neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua
+ で書かれている。以下にある通り、はっきり <literal>aliases</literal> と書かれている。
+ </para>
+ <para>
+ <link xl:href="https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124">https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124</link>
+ </para>
+ <programlisting language="lua" linenumbering="unnumbered">
+ <![CDATA[
+ aliases = {
+ BufCreate = 'BufAdd',
+ BufRead = 'BufReadPost',
+ BufWrite = 'BufWritePre',
+ FileEncoding = 'EncodingChanged',
+ },
+ ]]>
+ </programlisting>
+ <para>
+ ところで、上では取り上げなかった <literal>FileEncoding</literal> だが、これは
+ <literal>:help FileEncoding</literal> にしっかりと書いてある。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ *FileEncoding*
+ FileEncoding Obsolete. It still works and is equivalent
+ to |EncodingChanged|.
+ ]]>
+ </literallayout>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>まとめ</title>
+ <para>
+ 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。
+ </para>
+ <itemizedlist>
+ <listitem>
+ <literal>BufAdd</literal>/<literal>BufCreate</literal>
+ <itemizedlist>
+ <listitem>→ <literal>BufCreate</literal> は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (<literal>BufAdd</literal>) を使う</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <literal>BufRead</literal>/<literal>BufReadPost</literal>
+ <itemizedlist>
+ <listitem>→ <literal>BufReadPre</literal> との対称性のため、あるいは <literal>BufWritePost</literal> との対称性のため <literal>BufReadPost</literal> を使う</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <literal>BufWrite</literal>/<literal>BufWritePre</literal>
+ <itemizedlist>
+ <listitem>→ <literal>BufWritePost</literal> との対称性のため、あるいは <literal>BufReadPre</literal> との対称性のため <literal>BufWritePre</literal> を使う</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <literal>FileEncoding</literal>/<literal>EncodingChanged</literal>
+ <itemizedlist>
+ <listitem>→ <literal>FileEncoding</literal> は <literal>`Obsolete'' と明言されているので、`EncodingChanged</literal> を使う</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ ところでこの調査で知ったのだが、<literal>BufRead</literal> と <literal>BufWrite</literal>
+ は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら
+ <literal>Pre</literal>/<literal>Post</literal> 付きのものを使った方が分かりやすいだろう。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml
new file mode 100644
index 00000000..c4c26ca8
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>Vimで選択した行の順番を入れ替える</title>
+ <abstract>
+ Vim で選択した行の順番を入れ替える方法。
+ </abstract>
+ <keywordset>
+ <keyword>vim</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2021-10-02</date>
+ <revremark>Qiita から移植</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <link xl:href="https://qiita.com/nsfisis/items/4fefb361d9a693803520">https://qiita.com/nsfisis/items/4fefb361d9a693803520</link>
+ </note>
+ <section xml:id="tl-dr">
+ <title>TL; DR</title>
+ <programlisting language="vim" linenumbering="unnumbered">
+ <![CDATA[
+ " License: Public Domain
+
+ command! -bar -range=%
+ \ Reverse
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="version">
+ <title>バージョン情報</title>
+ <para>
+ <literal>:version</literal> の一部
+ </para>
+ <blockquote>
+ <para>
+ VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS
+ version Included patches: 1-148 Huge version without GUI.
+ </para>
+ </blockquote>
+ </section>
+ <section xml:id="existing-solution">
+ <title>よく紹介されている手法</title>
+ <section xml:id="existing-solution--external-commands">
+ <title><literal>tac</literal> / <literal>tail</literal></title>
+ <para>
+ <literal>tac</literal> や <literal>tail -r</literal> などの外部コマンドを <literal>!</literal>
+ を使って呼び出し、置き換える。
+ </para>
+ <blockquote>
+ <para>
+ :h v_!
+ </para>
+ </blockquote>
+ <para>
+ <literal>tac</literal> コマンドや <literal>tail</literal> の <literal>-r</literal>
+ オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい
+ </para>
+ </section>
+ <section xml:id="existing-solution--global-command">
+ <title><literal>:g/^/m0</literal></title>
+ <para>
+ こちらは外部コマンドに頼らず、Vim の機能のみを使う。<literal>g</literal> は <literal>:global</literal>
+ コマンドの、<literal>m</literal> は <literal>:move</literal> コマンドの略
+ </para>
+ <para>
+ <literal>:global</literal> コマンドは <literal>:[range]global/{pattern}/[command]</literal>
+ のように使い、<literal>[range]</literal> で指定された範囲の行のうち、<literal>{pattern}</literal>
+ で指定された検索パターンにマッチする行に対して、順番に <literal>[command]</literal>
+ で指定された Ex コマンドを呼び出す。
+ </para>
+ <blockquote>
+ <para>
+ :h :global
+ </para>
+ </blockquote>
+ <para>
+ <literal>:move</literal> コマンドは <literal>[range]:move {address}</literal> のように使い、<literal>[range]</literal>
+ で指定された範囲の行を <literal>{address}</literal> で指定された位置に移動させる。
+ </para>
+ <blockquote>
+ <para>
+ :h :move
+ </para>
+ </blockquote>
+ <para>
+ <literal>:g/^/m0</literal> のように組み合わせると、「すべての行を1行ずつ
+ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。
+ </para>
+ <para>
+ なお、<literal>:g/^/m0</literal> は全ての行を入れ替えるが、<literal>:N,Mg/^/mN-1</literal> とすることで
+ N行目から
+ M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。
+ </para>
+ <programlisting language="vim" linenumbering="unnumbered">
+ <![CDATA[
+ command! -bar -range=%
+ \ Reverse
+ \ <line1>,<line2>g/^/m<line1>-1
+ ]]>
+ </programlisting>
+ <para>
+ これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。
+ </para>
+ </section>
+ </section>
+ <section xml:id="problem-of-global-command">
+ <title><literal>:g/^/m0</literal> の問題点</title>
+ <para>
+ <literal>:global</literal>
+ コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。<literal>^</literal>
+ は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。<literal>'hlsearch'</literal>
+ オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと
+ <literal>n</literal> コマンドなどの際に不便である。
+ </para>
+ <blockquote>
+ <para>
+ :h @/
+ </para>
+ </blockquote>
+ </section>
+ <section xml:id="solution">
+ <title>解決策</title>
+ <blockquote>
+ <para>
+ [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した
+ </para>
+ </blockquote>
+ <para>
+ 前述した <literal>:Reverse</literal> コマンドの定義を少し変えて、次のようにする:
+ </para>
+ <programlisting language="vim" linenumbering="unnumbered">
+ <![CDATA[
+ function! s:reverse_lines(from, to) abort
+ execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1)
+ endfunction
+
+ command! -bar -range=%
+ \ Reverse
+ \ call <SID>reverse_lines(<line1>, <line2>)
+ ]]>
+ </programlisting>
+ <para>
+ 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。
+ </para>
+ <para>
+ この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが
+ <literal>^</literal> で上書きされることがなくなる。
+ </para>
+ <para>
+ Vim のヘルプから該当箇所を引用する (強調は筆者による)。
+ </para>
+ <blockquote>
+ <para>
+ :h autocmd-searchpat
+ </para>
+ <para>
+ <emphasis role="strong">Autocommands do not change the current search patterns.</emphasis> Vim saves the
+ current search patterns before executing autocommands then restores them
+ after the autocommands finish. This means that autocommands do not
+ affect the strings highlighted with the `hlsearch' option.
+ </para>
+ </blockquote>
+ <para>
+ これは autocommand
+ の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは
+ <literal>:nohlsearch</literal> のヘルプにある。同じく該当箇所を引用する
+ (強調は筆者による)。
+ </para>
+ <blockquote>
+ <para>
+ :h :nohlsearch
+ </para>
+ <para>
+ (略) This command doesn’t work in an autocommand, because the
+ highlighting state is saved and restored when executing autocommands
+ |autocmd-searchpat|. <emphasis role="strong">Same thing for when invoking a user function.</emphasis>
+ </para>
+ </blockquote>
+ <para>
+ この仕様により、<literal>:g/^/m0</literal>
+ の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。
+ </para>
+ </section>
+ <section xml:id="solution-revised">
+ <title>解決策 (改訂版)</title>
+ <blockquote>
+ <para>
+ [2020/9/28追記] より簡潔な方法を見つけたため追記する
+ </para>
+ </blockquote>
+ <programlisting language="vim" linenumbering="unnumbered">
+ <![CDATA[
+ command! -bar -range=%
+ \ Reverse
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+ ]]>
+ </programlisting>
+ <para>
+ まさにこのための Exコマンド、<literal>:keeppatterns</literal>
+ が存在する。<literal>:keeppatterns {command}</literal>
+ のように使い、読んで字の如く、後ろに続く
+ Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
+ </para>
+ <blockquote>
+ <para>
+ :h :keeppatterns
+ </para>
+ </blockquote>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml
new file mode 100644
index 00000000..898d7ea9
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml
@@ -0,0 +1,563 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2022 トークン問題の解説</title>
+ <abstract>
+ PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。
+ </abstract>
+ <keywordset>
+ <keyword>conference</keyword>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-04-09</date>
+ <revremark>公開</revremark>
+ </revision>
+ <revision>
+ <date>2022-04-16</date>
+ <revremark>2問目、3問目の解説を追加、1問目に加筆</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 本日開始された <link xl:href="https://phperkaigi.jp/2022/">PHPerKaigi 2022</link> の PHPer
+ チャレンジにおいて、弊社
+ <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> の問題を
+ 3問作成した。この記事では、これらの問題の解説をおこなう。
+ </para>
+ <para>
+ リポジトリはこちら: <link xl:href="https://github.com/nsfisis/PHPerKaigi2022-tokens">https://github.com/nsfisis/PHPerKaigi2022-tokens</link>
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck">
+ <title>第1問 brainf_ck.php</title>
+ <para>
+ ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ declare(strict_types=0O1);
+
+ namespace Dgcircus\PHPerKaigi\Y2022;
+
+ /**
+ * @todo
+ * Run this program to acquire a PHPer token.
+ */
+
+ https://creativecommons.org/publicdomain/zero/1.0/
+
+ \error_reporting(~+!'We are hiring!');
+
+ $z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
+ $id = \spl_object_id(...);
+ $put = fn($c) => \printf('%c', $c);
+ $mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
+
+ $👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
+ $👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
+ $👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
+ $👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
+ $📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
+ $🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+ +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+ $b => $loop(++$pc, ++$n),
+ $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
+ default => $loop(++$pc, $n),
+ })($pc, -![])],
+ default => [$mp, ++$pc],
+ };
+ $🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+ +!![] => [$mp, ++$pc],
+ default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+ $e => $loop(--$pc, ++$n),
+ $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
+ default => $loop(--$pc, $n),
+ })($pc, -![])],
+ };
+ $🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
+ isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
+ )($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
+
+ $🐘([
+ $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+ $🤡,
+ $👉, $👍, $👍, $👍,
+ $👉, $👍, $👍, $👍, $👍, $👍,
+ $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+ $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+ $👈, $👈, $👈, $👈, $👎,
+ $🎪,
+ $👉, $👍, $👍, $👍, $👍, $👍, $📝,
+ $👎, $👎, $📝,
+ $👉, $👎, $👎, $👎, $📝,
+ $👉, $👎, $👎, $👎, $📝,
+ $👎, $👎, $📝,
+ $👎, $📝,
+ $👈, $📝,
+ $👉, $👉, $👎, $👎, $📝,
+ $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
+ $👈, $👎, $👎, $👎, $👎, $📝,
+ $👈, $📝,
+ $👉, $👍, $👍, $📝,
+ $👉, $👎, $📝,
+ $👈, $📝,
+ ]);
+ ]]>
+ </programlisting>
+ <para>
+ この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
+ </para>
+ <section xml:id="q1-brainfuck--commentary">
+ <title>解説</title>
+ <section xml:id="q1-brainfuck--commentary--emoji">
+ <title>絵文字</title>
+ <para>
+ まず目につくのは大量の絵文字だろう。 PHP
+ は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--brainfuck">
+ <title>プログラム全体</title>
+ <para>
+ Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck
+ とは、難解プログラミング言語のひとつであり、ここで説明するよりも
+ Wikipedia の該当ページを読んだ方がよい。
+ </para>
+ <para>
+ <link xl:href="https://ja.wikipedia.org/wiki/Brainfuck">https://ja.wikipedia.org/wiki/Brainfuck</link>
+ </para>
+ <para>
+ なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ + + + + + + + + + +
+ [
+ > + + +
+ > + + + + +
+ > + + + + + + + + + + + +
+ > + + + + + + + + + +
+ < < < < -
+ ]
+ > + + + + + .
+ - - .
+ > - - - .
+ > - - - .
+ - - .
+ - .
+ < .
+ > > - - .
+ + + + + + + + .
+ < - - - - .
+ < .
+ > + + .
+ > - .
+ < .
+ ]]>
+ </literallayout>
+ <para>
+ 実行結果はこちら: <link xl:href="https://ideone.com/22VWmb">https://ideone.com/22VWmb</link>
+ </para>
+ <para>
+ それぞれの絵文字で表された関数が、各命令に対応している。
+ </para>
+ <itemizedlist>
+ <listitem><literal>$👉</literal>: <literal>&gt;</literal></listitem>
+ <listitem><literal>$👈</literal>: <literal>&lt;</literal></listitem>
+ <listitem><literal>$👍</literal>: <literal>+</literal></listitem>
+ <listitem><literal>$👎</literal>: <literal>-</literal></listitem>
+ <listitem><literal>$📝</literal>: <literal>.</literal></listitem>
+ <listitem><literal>$🤡</literal>: <literal>[</literal></listitem>
+ <listitem><literal>$🎪</literal>: <literal>]</literal></listitem>
+ </itemizedlist>
+ <para>
+ <literal>,</literal> (入力) に対応する関数はない
+ (このプログラムでは使わないので用意していない)。
+ </para>
+ <para>
+ なお、<literal>$🐘</literal> はいわゆる main 関数であり、プログラムの実行部分である。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--emoji-selection">
+ <title>絵文字の選択</title>
+ <para>
+ おおよそ意味に合致するよう選んでいるが、<literal>$🤡</literal> と <literal>$🎪</literal>
+ は弊社デジタルサーカスにちなんでいる。 また、<literal>$🐘</literal> は PHP
+ のマスコットの象に由来する。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--strict-types">
+ <title>strict_types</title>
+ <para>
+ <literal>declare</literal> 文の <literal>strict_types</literal> に指定できるのは、<literal>0</literal> か <literal>1</literal>
+ の数値リテラルだが、 <literal>0x0</literal> や <literal>0b1</literal> のような値も受け付ける。 今回は、PHP
+ 8.1 から追加された、<literal>0O</literal> または <literal>0o</literal> から始まる八進数リテラルを使った。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--url">
+ <title>URL</title>
+ <para>
+ ソースコードのライセンスを示したこの部分だが、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ https://creativecommons.org/publicdomain/zero/1.0/
+ ]]>
+ </programlisting>
+ <para>
+ 完全に合法な PHP のコードである。 <literal>https:</literal> 部分はラベル、<literal>//</literal>
+ 以降は行コメントになっている。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--numbers">
+ <title>リテラルなしで数値を生成する</title>
+ <para>
+ ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。
+ PHP では、型変換を利用することで任意の整数を作り出すことができる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ assert(0 === +!![]);
+ assert(1 === +![]);
+ assert(2 === ![]+![]);
+ assert(3 === ![]+![]+![]);
+ assert(10 === +(![].+!![]));
+ ]]>
+ </programlisting>
+ <para>
+ <literal>[]</literal> に <literal>!</literal> を適用すると <literal>true</literal> が返ってくる。それに <literal>+</literal>
+ を適用すると、<literal>bool</literal> から <literal>int</literal> ヘの型変換が走り、<literal>1</literal> が生成される。<literal>10</literal>
+ はさらにトリッキーだ。まず <literal>1</literal> と <literal>0</literal> を作り、<literal>.</literal> で文字列として結合する
+ (<literal>'10'</literal>)。これに <literal>+</literal> を適用すると、<literal>string</literal> から <literal>int</literal>
+ への型変換が走り、<literal>10</literal> が生まれる (コード量に頓着しないなら、<literal>1</literal> を 10
+ 個足し合わせてももちろん 10 が作れる)。
+ </para>
+ <para>
+ また、<literal>error_reporting</literal> に指定しているのは <literal>-1</literal> である。 これは、<literal>!</literal>
+ によって文字列を <literal>false</literal> にし、<literal>+</literal> によって <literal>false</literal> を <literal>0</literal>
+ にし、さらにビット反転して <literal>-1</literal> にしている。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--conditionals">
+ <title><literal>if</literal> 文なしで条件分岐</title>
+ <para>
+ 三項演算子ないし <literal>match</literal> 式を使うことで、<literal>if</literal>
+ を一切書かずに条件分岐ができる。 また、<literal>&amp;&amp;</literal> / <literal>||</literal> も使えることがある。
+ 遅延評価が不要なケースでは、<literal>[$t, $f][$cond]</literal>
+ のような形で分岐することもできる。
+ </para>
+ </section>
+ <section xml:id="q1-brainfuck--commentary--loops">
+ <title><literal>while</literal>、<literal>for</literal> 文なしでループ</title>
+ <para>
+ 不動点コンビネータを使って無名再帰する
+ (詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に
+ Z コンビネータとして知られるものを使った (<literal>$z</literal>)。
+ </para>
+ <para>
+ 実際のところ、<literal>$🤡</literal> や <literal>$🎪</literal>、<literal>$🐘</literal> は、一度 Scheme (Lisp の一種)
+ で書いてから PHP に翻訳する形で記述した。
+ </para>
+ <para>
+ なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは)
+ ので、 あまりに長い brainf*ck
+ プログラムを書くとスタックオーバーフローする。
+ </para>
+ </section>
+ </section>
+ </section>
+ <section xml:id="q2-riddle">
+ <title>第2問 riddle.php</title>
+ <para>
+ ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ /*********************************************************
+ * This program displays a PHPer token. *
+ * Guess 'N'. *
+ * *
+ * Hints: *
+ * - N itself has no special meaning, e.g., 42, 8128, *
+ * it is selected at random. *
+ * - Each element of $token represents a single letter. *
+ * - One letter consists of 5x5 cells. *
+ * - Remember, the output is a complete PHPer token. *
+ * *
+ * License: *
+ * https://creativecommons.org/publicdomain/zero/1.0/ *
+ *********************************************************/
+ const N = 0 /* Change it to your answer. */;
+ assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+
+ $token = [
+ 0x14B499C,
+ 0x0BE34CC, 0x01C9C69,
+ 0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
+ 0x01C1C66, 0x0FC1C47, 0x01C1C66,
+ 0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
+ ];
+ foreach ($token as $x) {
+ $x = $x ^ N;
+
+ $x = sprintf('%025b', $x);
+ $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+ $x = implode("\n", str_split($x, length: 5));
+ echo "{$x}\n\n";
+ }
+ ]]>
+ </programlisting>
+ <para>
+ さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。
+ トークンを得るためには、ソースコードを読み、定数 <literal>N</literal>
+ を特定する必要がある。
+ </para>
+ <para>
+ ここでは、私の想定解を解説する。
+ </para>
+ <section xml:id="q2-riddle--code-reading">
+ <title>読解</title>
+ <para>
+ まずはソースコードを読んでいく。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $token = [
+ // 略
+ ];
+ ]]>
+ </programlisting>
+ <para>
+ 数値からなる <literal>$token</literal> があり、各要素をループしている。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x = $x ^ N;
+ ]]>
+ </programlisting>
+ <para>
+ まずは排他的論理和 (xor) を取り、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x = sprintf('%025b', $x);
+ ]]>
+ </programlisting>
+ <para>
+ 二進数に変換して、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+ ]]>
+ </programlisting>
+ <para>
+ 0 を空白に、1 を <literal>#</literal> にし、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x = implode("\n", str_split($x, length: 5));
+ ]]>
+ </programlisting>
+ <para>
+ 5文字ごとに区切ったあと、改行で結合している。
+ </para>
+ </section>
+ <section xml:id="q2-riddle--hint">
+ <title>ヒント</title>
+ <para>
+ 次に、ソースコードに書いてあるヒントを読んでいく。
+ </para>
+ <itemizedlist>
+ <listitem><literal>N</literal> それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている</listitem>
+ <listitem><literal>$token</literal> の各要素は、1文字を表す</listitem>
+ <listitem>1文字は 5x5 のセルからなる</listitem>
+ <listitem>出力されるのは、完全な PHPer トークンである</listitem>
+ </itemizedlist>
+ <para>
+ ここで、PHPer トークンは必ず <literal>#</literal> 記号から始まることを思いだすと、
+ <literal>$token</literal> の最初の数字 <literal>0x14B499C</literal> は、変換の結果 <literal>#</literal>
+ になるのではないかと予想される (なお、このことは、リポジトリの README
+ ファイルに追加ヒントとして書かれている)。
+ </para>
+ </section>
+ <section xml:id="q2-riddle--solve">
+ <title>解く</title>
+ <para>
+ ここまでわかれば、あと一歩で解ける。すなわち、<literal>0x14B499C</literal> が <literal>#</literal>
+ に変換されるような <literal>N</literal> を見つければよい。
+ </para>
+ <para>
+ <literal>N</literal> は高々
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+ ]]>
+ </programlisting>
+ <para>
+ なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $x = 0x14B499C;
+
+ $x = $x ^ N;
+
+ $x = sprintf('%025b', $x);
+ $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+ $x = implode("\n", str_split($x, length: 5));
+
+ assert($x ===
+ " # # \n" .
+ "#####\n" .
+ " # # \n" .
+ "#####\n" .
+ " # # ");
+ ]]>
+ </programlisting>
+ <para>
+ この一連の変換に対する逆変換を考えると、次のようになる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $x =
+ " # # \n" .
+ "#####\n" .
+ " # # \n" .
+ "#####\n" .
+ " # # ";
+
+ $x = implode('', explode("\n", $x));
+ $x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x);
+ $x = bindec($x);
+
+ $n = $x ^ 0x14B499C;
+
+ echo "N = $n\n";
+ ]]>
+ </programlisting>
+ <para>
+ これを実行すると、<literal>N</literal> が得られる。
+ </para>
+ </section>
+ </section>
+ <section xml:id="q3-toquine">
+ <title>第3問 toquine.php</title>
+ <para>
+ ソースコードはこちら。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ // License: https://creativecommons.org/publicdomain/zero/1.0/
+ // This is a quine-like program to generate a PHPer token.
+ // Execute it like this: php toquine.php | php | php | php | ...
+
+ $s = <<<'Q'
+ <?cuc
+ // Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/
+ // Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra.
+ // Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ...
+ %f$f = %f;
+ $f = fge_ebg13($f); $kf = [
+ %f,
+ ];
+ $g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr
+ $g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], [' ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a";
+ $jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
+ cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
+ Q;
+ $s = str_rot13($s); $xs = [
+ 0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
+ 0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
+ 0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
+ ];
+ $t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
+ $t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
+ $ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
+ printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
+ ]]>
+ </programlisting>
+ <para>
+ コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ php toquine.php | php | php | php | ...
+ ]]>
+ </programlisting>
+ <para>
+ 実際にはもう少しパイプで繋げなければならない。
+ </para>
+ <section xml:id="q3-toquine--commentary">
+ <title>解説</title>
+ <section xml:id="q3-toquine--commentary--quine">
+ <title>プログラム全体</title>
+ <para>
+ コメントにもあるとおり、これは quine (風) のプログラムになっている。
+ Quine
+ とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。
+ </para>
+ <para>
+ このプログラムは、実行すると自身とほとんど同じプログラムを出力する。
+ 異なるのはトークンになっている部分のみである。
+ </para>
+ </section>
+ <section xml:id="q3-toquine--commentary--tokens">
+ <title>トークン</title>
+ <para>
+ <literal>$xs</literal> がトークンに対応している。変換のロジックは <literal>riddle.php</literal>
+ とほぼ同じなので省略する。
+ </para>
+ </section>
+ <section xml:id="q3-toquine--commentary--states">
+ <title>状態保持</title>
+ <para>
+ トークンの何文字目まで出力したかを、ソースコードを変えずに (quine
+ なので) 覚えておく必要がある。
+ このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、<literal><emphasis>LINE</emphasis></literal>
+ から情報を取得している。
+ </para>
+ </section>
+ <section xml:id="q3-toquine--commentary--rot-13">
+ <title>ROT 13</title>
+ <para>
+ Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。
+ これがあまり美しくないので、<literal>toquine.php</literal> では、ROT 13
+ 変換を使って難読化した。
+ </para>
+ <para>
+ それにしてもなぜこんなものが標準ライブラリに……。
+ </para>
+ </section>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
+ </para>
+ <para>
+ 今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、
+ 来年は 5問、より面白い問題を持っていきます。
+ </para>
+ <para>
+ 実はもう作りはじめているので、どうか来年もありますように……。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml
new file mode 100644
index 00000000..34082b12
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>term-banner: ターミナルにバナーを表示するツールを書いた</title>
+ <abstract>
+ ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。
+ </abstract>
+ <revhistory>
+ <revision>
+ <date>2022-04-24</date>
+ <revremark>公開</revremark>
+ </revision>
+ <revision>
+ <date>2022-04-27</date>
+ <revremark>-f オプションについて追記</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ こんなものを作った。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
+ ]]>
+ </literallayout>
+ <para>
+ image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner
+ のスクリーンショット]
+ </para>
+ <para>
+ コマンドライン引数として渡した文字列をターミナルに大きく表示する。
+ </para>
+ <para>
+ リポジトリはこちら: <link xl:href="https://github.com/nsfisis/term-banner">https://github.com/nsfisis/term-banner</link>
+ </para>
+ </section>
+ <section xml:id="motivation">
+ <title>Motivation</title>
+ <para>
+ 以前、https://github.com/nsfisis/big-clock-mode[big-clock-mode]
+ という似たようなプログラムを書いた。 これは tmux の <literal>:clock-mode</literal>
+ コマンドに着想を得たもので、<literal>:clock-mode</literal>
+ よりも大きく現在時刻を表示する。
+ </para>
+ <para>
+ <literal>big-clock-mode</literal>
+ を開発したのは、次のようなシチュエーションで使うためである。
+ 弊社では現在リモートワークが基本だが、web
+ 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。
+ こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。
+ </para>
+ <para>
+ それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。
+ </para>
+ <para>
+ しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。
+ どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。
+ </para>
+ <para>
+ そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。
+ まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。
+ </para>
+ </section>
+ <section xml:id="program">
+ <title>プログラム</title>
+ <para>
+ 全体の流れは次のようになっている。
+ </para>
+ <orderedlist numeration="arabic">
+ <listitem>フォントファイルを読み込む</listitem>
+ <listitem>コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS 基準で並んでいるため)</listitem>
+ <listitem>1文字ずつレンダリングしていく</listitem>
+ </orderedlist>
+ <para>
+ <literal>big-clock-mode</literal> が Go 製なので、今回も Go で書いた。 PNG
+ が標準ライブラリにあったり、Shift-JIS
+ のエンコーディングが準標準ライブラリにあったりしたのは助かった。
+ </para>
+ <para>
+ フォントファイルは <literal>go:embed</literal>
+ で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。
+ 仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。
+ </para>
+ </section>
+ <section xml:id="font">
+ <title>フォント</title>
+ <para>
+ フリーの 8x8
+ ビットマップフォントである、https://littlelimit.net/misaki.htm[美咲フォント
+ 2021-05-05a 版] を使わせていただいた。
+ </para>
+ <para>
+ はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。
+ 同じく 8x8
+ で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。
+ </para>
+ <para>
+ 美咲フォントは、平仮名・片仮名に留まらず、JIS
+ 第一・第二水準の漢字までサポートしている。
+ 第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。
+ </para>
+ <para>
+ さらに言うと、実のところ美咲フォントは実サイズ 7x7
+ で作られており、余白が設けられている。
+ これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。
+ おかげでコーディングまで楽になった。
+ </para>
+ <para>
+ ゴシック体と明朝体があったが、私の好みで明朝体の方にした。
+ ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。
+ </para>
+ <para>
+ 2022-04-27 追記: <literal>-f</literal> オプションで選べるようにした。
+ </para>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ あなたもターミナルに住んでみませんか?
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml
new file mode 100644
index 00000000..1c3b015d
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2022</title>
+ <abstract>
+ 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。
+ </abstract>
+ <keywordset>
+ <keyword>conference</keyword>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-05-01</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 2022-04-09 から 2022-04-11 にかけて開催された、<link xl:href="https://phperkaigi.jp/2022/">PHPerKaigi 2022</link> に、
+ 一般参加者として参加した。
+ 弊社<link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link>はダイヤモンドスポンサーとなっており、
+ スポンサー枠のチケットを使わせていただいた。
+ </para>
+ <para>
+ 昨年のレポートは<link xl:href="/posts/2021-03-30/phperkaigi-2021">こちら</link>。
+ </para>
+ </section>
+ <section xml:id="comments">
+ <title>感想</title>
+ <section xml:id="comments--great-sessions">
+ <title>厳選おすすめトーク</title>
+ <para>
+ 多くの素晴らしいトークの中から、特におすすめのものを 5つ選んだ。
+ 是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。
+ </para>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2022/proposal/ef8cf4ed-63fe-42f8-8145-b3e70054458b">予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント</link>
+ </para>
+ <blockquote>
+ <para>
+ PHP はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。
+ </para>
+ <para>
+ 本講演では PHP 8.1 をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。
+ </para>
+ </blockquote>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2022/proposal/db00d49e-0dd6-453f-b54b-f731d112f10e">PHPのエラーを理解して適切なエラーハンドリングを学ぼう</link>
+ </para>
+ <blockquote>
+ <para>
+ PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice 理解していますか?<br/>
+ これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!<br/>
+ またそれらを理解した上でのエラーハンドリングを学びましょう。
+ </para>
+ </blockquote>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2022/proposal/4a7e3ded-9134-4919-955c-ec7bf4491c0d">エラー監視とテスト体制への改善作戦</link>
+ </para>
+ <blockquote>
+ <para>
+ 毎日流れてくるエラーに皆さんはどう向き合ってますか?<br/>
+ エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。<br/>
+ サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、<br/>
+ エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。
+ </para>
+ </blockquote>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2022/proposal/6f47daf8-c78f-4fb1-9b99-e9656e6fe7f7">ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか</link>
+ </para>
+ <blockquote>
+ <para>
+ 昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。
+ </para>
+ <para>
+ 最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には<br/>
+ ・「(私の思う)良い設計」を実現するための意思決定<br/>
+ ・「ISUCONの問題」という位置付けに由来する取捨選択<br/>
+ ・移植中に遭遇したトラブルとその解決策<br/>
+ といった文脈や葛藤が存在しています。
+ </para>
+ <para>
+ 本発表はそれらを共有することで<br/>
+ ・PHPアプリケーションの設計、実装事例として役立ててもらう<br/>
+ ・ISUCONの言語移植に興味を持ってもらう<br/>
+ ・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう<br/>
+ ことを目的とします。
+ </para>
+ </blockquote>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2022/proposal/5a260e4e-542d-4d82-849d-ef3d6cb7c854">チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた</link>
+ </para>
+ <blockquote>
+ <para>
+ サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。
+ </para>
+ <para>
+ フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。
+ </para>
+ <para>
+ このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。
+ </para>
+ </blockquote>
+ </section>
+ <section xml:id="comments--token-quizzes">
+ <title>トークン問題の作成</title>
+ <para>
+ 今回は、PHPer チャレンジ用に弊社のトークン問題を 3題作成した。
+ こちらについては<link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens">別途記事にしている</link>ので、そちらを参照されたい。
+ </para>
+ </section>
+ <section xml:id="comments--phper-challenge">
+ <title>PHPer チャレンジ</title>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2022/challenge">1位</link>になった。<br/>
+ また、賞品として <link xl:href="https://www.amazon.co.jp/dp/B08MQNJC9Z">Echo Show 15</link> をいただいた。
+ </para>
+ </section>
+ <section xml:id="comments--conference">
+ <title>カンファレンス全体への感想</title>
+ <para>
+ <link xl:href="/posts/2021-03-30/phperkaigi-2021">去年の参加レポ</link> では、こんなことを書いた。
+ </para>
+ <blockquote>
+ <para>
+ 1つ個人的な反省点としては、(中略) Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、
+ 後から見返せる発表やスライドに注力してしまった、ということだ。
+ 発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。<br/>
+ まあ初カンファレンスだし、とお茶を濁しておこう。
+ </para>
+ </blockquote>
+ <para>
+ この反省を踏まえ、今年は積極的にほかの場 (公式の Discord サーバや、アンカンファレンス) にも参加した。<br/>
+ これにより、参加体験の質がはるかに向上した。特に Discord に関しては、登壇者ご本人による補足や、
+ 質問への回答などがおこなわれる (ことが多い) ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。
+ </para>
+ <para>
+ なお、アンカンファレンスについては、1日目の終わりに<link xl:href="https://fortee.jp/phperkaigi-2022/unconference/view/d332797a-8921-4706-a7e2-ee72640c9b5e">トークン問題の解説放送</link>もおこなった。
+ </para>
+ <para>
+ また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。
+ 今年は 3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。
+ </para>
+ </section>
+</section>
+<section xml:id="next-year">
+ <title>そして来年へ……?</title>
+ <para>
+ PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。
+ </para>
+ <itemizedlist>
+ <listitem>プロポーザルを出す</listitem>
+ <listitem>PHPer チャレンジのトークン問題を 5題作成する</listitem>
+ <listitem>現地に行く</listitem>
+ <listitem>PHPer チャレンジで圧勝する</listitem>
+ </itemizedlist>
+ <para>
+ <hr/>
+ </para>
+ <para>
+ 最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。
+ </para>
+ <para>
+ ではまた来年。
+ </para>
+</section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml
new file mode 100644
index 00000000..ba5b7026
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</title>
+ <abstract>
+ PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。
+ </abstract>
+ <keywordset>
+ <keyword>conference</keyword>
+ <keyword>php</keyword>
+ <keyword>phpcon</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-08-27</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 本日 <link xl:href="https://phpcon.okinawa.jp/">PHP カンファレンス沖縄 2022</link> が開催された (らしい)。
+ </para>
+ <para>
+ カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。
+ </para>
+ <itemizedlist>
+ <listitem>ツイート: <link xl:href="https://twitter.com/m3m0r7/status/1563397620231712772">https://twitter.com/m3m0r7/status/1563397620231712772</link></listitem>
+ <listitem>スライド: <link xl:href="https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3">https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3</link></listitem>
+ </itemizedlist>
+ </section>
+ <section xml:id="solution">
+ <title>解</title>
+ <para>
+ 細かいレギュレーションは不明だったので、勝手に定めた。
+ </para>
+ <itemizedlist>
+ <listitem>コマンドライン引数の第1引数で受けとる</listitem>
+ <listitem>結果は標準出力に出す</listitem>
+ <listitem>コンマの直後にはスペースを1つ置く</listitem>
+ <listitem>末尾コンマは禁止</listitem>
+ <listitem>数字でないものは入ってこないものとする</listitem>
+ <listitem>負数は入ってこないものとする</listitem>
+ </itemizedlist>
+ <para>
+ 書いたものがこちら:
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ [<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
+ ]]>
+ </programlisting>
+ <para>
+ しめて 123 バイトとなった (末尾改行を含めずにカウント)。
+ </para>
+ <para>
+ こちらは改行とスペースを追加したバージョン:
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ [<?php
+
+ $n = $argv[1];
+ foreach ([1e4, 5e3, 2e3, 1e3, 500, 100, 50, 10, 5, 1] as $x)
+ for (; $n >= $x; $n -= $x)
+ $r[] = $x;
+ echo implode(', ', $r ?? []);
+
+ ?>]
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="techniques">
+ <title>使用したテクニック</title>
+ <section xml:id="techniques--exponential-notation">
+ <title>指数表記</title>
+ <para>
+ 割と多くの言語のゴルフで使えるテクニック。
+ <literal>e</literal> を用いた指数表記で、大きな数を短く表す。
+ このコードでは <literal>10000</literal>、<literal>5000</literal>、<literal>2000</literal>、<literal>1000</literal> を指数表記している。
+ </para>
+ </section>
+ <section xml:id="techniques--shorten-loop">
+ <title>foreach や for の中身を1つの文に</title>
+ <para>
+ <literal>foreach</literal>、<literal>for</literal>、<literal>if</literal> などの後ろには、
+ 通常 <literal>{</literal> を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、<literal>{</literal> と <literal>}</literal> を省略できる。
+ C言語などでも使える。
+ </para>
+ </section>
+ <section xml:id="techniques--omit-initialization">
+ <title>$r に初期値を入れない</title>
+ <para>
+ PHP では、<literal>$r[] = ......</literal> のような配列の末尾に追加する式を実行したとき、
+ <literal>$r</literal> が未定義だった場合は <literal>$r</literal> を勝手に定義して空の配列で初期化してくれる。
+ これを利用すると、<literal>$r = [];</literal> のような初期化が不要になる。
+ </para>
+ <para>
+ ただし、プログラムに 0 が渡されるとループを一度も回らないので、<literal>$r</literal> が未定義になってしまい、
+ <literal>implode()</literal> に渡すところでエラーになる。
+ それを防ぐために <literal>$r ?? []</literal> を使っている。
+ </para>
+ <para>
+ もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。
+ </para>
+ </section>
+ <section xml:id="techniques--put-text-outside-php-tag">
+ <title>PHP タグの外に文字列を置く</title>
+ <para>
+ PHP では、<literal>&lt;?php</literal> <literal>?&gt;</literal> で囲われた部分の外側にある文字列は、そのまま出力される。
+ 今回のケースでは、先頭と末尾に必ず <literal>[</literal> と <literal>]</literal> を出力するので、そのまま書いてやればよい。
+ </para>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 最後になりましたが、<link xl:href="https://twitter.com/m3m0r7">めもりー</link>さん、楽しい問題をありがとうございました。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml
new file mode 100644
index 00000000..eb9353d1
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>弊社の PHP Foundation への寄付に寄せて</title>
+ <abstract>
+ 先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。
+ 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。
+ </abstract>
+ <revhistory>
+ <revision>
+ <date>2022-08-31</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ <emphasis role="strong">注: これは私個人の意見であり、所属する組織を代表するものではありません。</emphasis>
+ </para>
+ <para>
+ 先日、私の勤める <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> が
+ <link xl:href="https://opencollective.com/phpfoundation">PHP Foundation</link> へ $2,000 の寄付をおこないました。
+ </para>
+ <para>
+ 記事: <link xl:href="https://www.dgcircus.com/news/581">https://www.dgcircus.com/news/581</link>
+ </para>
+ <para>
+ 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。
+ </para>
+ </section>
+ <section xml:id="why">
+ <title>なぜ?</title>
+ <para>
+ 組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。
+ </para>
+ <para>
+ 当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:
+ </para>
+ <blockquote>
+ <para>
+ 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、
+ ↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、
+ 寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか
+ (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。
+ </para>
+ <para>
+ 追記: 「肩身が狭くなる」というのがより適切でした。
+ </para>
+ </blockquote>
+ <para>
+ ※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。
+ </para>
+ <para>
+ OSS を金銭的に支援したり、技術カンファレンスへ協賛したり
+ (あるいは <link xl:href="https://twitter.com/tomzoh">CTO</link> がカンファレンスを年2で主催したり:
+ <link xl:href="https://iosdc.jp">iOSDC</link> <link xl:href="https://phperkaigi.jp">PHPerKaigi</link>)
+ といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。
+ これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。
+ </para>
+ <para>
+ 以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。
+ </para>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。
+ この場を借りて感謝申し上げます。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml b/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml
new file mode 100644
index 00000000..88d71755
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml
@@ -0,0 +1,708 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【PHP】 fizzbuzz を書く。1行あたり2文字で。</title>
+ <abstract>
+ PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。
+ </abstract>
+ <keywordset>
+ <keyword>php</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-09-28</date>
+ <revremark>公開</revremark>
+ </revision>
+ <revision>
+ <date>2022-09-29</date>
+ <revremark>小さな文言の修正・変更</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>記事の構成について</title>
+ <para>
+ この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。
+ 最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、
+ <link xl:href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</link>
+ にソースコードがあるので、そちらを先に見てほしい。
+ </para>
+ </section>
+ <section xml:id="regulations">
+ <title>レギュレーション</title>
+ <para>
+ PHP で、次のような制約の下に fizzbuzz を書いた。
+ </para>
+ <itemizedlist>
+ <listitem>
+ 1行あたりの文字数は2文字までに収めること (ただし <literal>&lt;?php</literal> タグは除く)
+ <itemizedlist>
+ <listitem>
+ 厳密な定義: <literal>&lt;?php</literal> タグ以降のソースコードが、2 byte ごとにラインフィード (LF) で区切られること
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>スペースやタブを使用しないこと</listitem>
+ <listitem>
+ ループのアンロールをしないこと
+ <itemizedlist>
+ <listitem>100 回ループの代わりに 100 回コードをコピペ、というのは禁止</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>PHP 7.4〜8.1 で動作すること</listitem>
+ <listitem>実行時に Notice や Warning が出ないこと</listitem>
+ <listitem>標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)</listitem>
+ </itemizedlist>
+ <para>
+ 備考: PHP には <literal>short_open_tag</literal> というオプションがあり、
+ これを有効にするとファイル冒頭の <literal>&lt;?php</literal> の代わりに <literal>&lt;?</literal>
+ を使うことができ、文字どおり1行2文字で書ける。
+ ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
+ </para>
+ </section>
+ <section xml:id="problems">
+ <title>主な障害</title>
+ <para>
+ 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
+ </para>
+ <para>
+ 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
+ </para>
+ <programlisting language="c" linenumbering="unnumbered">
+ <![CDATA[
+ #\
+ i\
+ n\
+ c\
+ l\
+ u\
+ d\
+ e\
+ <\
+ s\
+ t\
+ d\
+ i\
+ o\
+ .\
+ h\
+ >\
+ /*
+ */
+ i\
+ n\
+ t\
+ /*
+ */
+ m\
+ a\
+ i\
+ n(
+ ){
+ f\
+ o\
+ r(
+ i\
+ n\
+ t\
+ /*
+ */
+ i=
+ 1;
+ i<
+ 1\
+ 0\
+ 0;
+ i\
+ +\
+ +)
+ if
+ (i
+ %\
+ 15
+ ==
+ 0)
+ p\
+ r\
+ i\
+ n\
+ t\
+ f(
+ "\
+ F\
+ i\
+ z\
+ z\
+ B\
+ u\
+ z\
+ z\
+ %\
+ c\
+ ",
+ 10
+ );
+
+ /* あとは同じように普通のプログラムを変形するだけなので省略 */
+ ]]>
+ </programlisting>
+ <para>
+ バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
+ </para>
+ <para>
+ さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。
+ これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。
+ 例えば、<literal>echo</literal> で出力することや、<literal>for</literal> でループすること、
+ <literal>new</literal> でインスタンスを生成することができない。
+ 特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。
+ </para>
+ <para>
+ 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <literal>_</literal>: <literal>gettext</literal> のエイリアス
+ </listitem>
+ <listitem>
+ <literal>dl</literal>: 拡張モジュールをロードする
+ </listitem>
+ <listitem>
+ <literal>pi</literal>: 円周率を返す
+ </listitem>
+ </itemizedlist>
+ <para>
+ (環境によって多少は変わるかも)
+ </para>
+ <para>
+ 2文字の関数を定義しまくった拡張モジュールを用意しておいて <literal>dl()</literal> で読み込む行為は、レギュレーションで定めた
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)</listitem>
+ </itemizedlist>
+ </blockquote>
+ <para>
+ に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。
+ </para>
+ <para>
+ また、2文字だと文字列がまともに書けないのも辛い。<literal>''</literal> だけで2文字使うので、
+ 「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $a
+ ='
+ a'
+ ;;
+ ]]>
+ </programlisting>
+ <para>
+ とすると <literal>$a</literal> は <literal>"\na"</literal> になるのだが、余計な改行が入ってしまう。
+ </para>
+ <para>
+ これらの障害をどのように乗り越えるのか、次節から見ていく。
+ </para>
+ </section>
+ <section xml:id="commentary">
+ <title>解説</title>
+ <section xml:id="commentary--normal-fizzbuzz">
+ <title>普通の (?) fizzbuzz</title>
+ <para>
+ まずは普通に書くとしよう。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ for ($i = 1; $i < 100; $i++) {
+ echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
+ </para>
+ </section>
+ <section xml:id="commentary--remove-keywords">
+ <title><literal>for</literal> の排除</title>
+ <para>
+ <literal>for</literal> は、3文字もある長いキーワードである。
+ こんなものは使えない。<literal>array_</literal> 系の関数を使って、適当に置き換えるとしよう。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $s = range(1, 100);
+ array_walk(
+ $s,
+ fn($i) =>
+ printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
+ );
+ ]]>
+ </programlisting>
+ <para>
+ <literal>array_walk</literal> や <literal>range</literal>、<literal>printf</literal> といった
+ <literal>for</literal> よりも長いトークンが現れてしまったが、これは次節で直すことにする。
+ なお、<literal>echo</literal> は文 (statement) であり式 (expression) ではないので、式である <literal>printf</literal> に置き換えた。
+ </para>
+ </section>
+ <section xml:id="commentary--shorten-function-invocation">
+ <title>関数呼び出しの短縮</title>
+ <para>
+ <literal>range</literal>、<literal>array_walk</literal>、<literal>printf</literal> は長すぎるのでどうにかせねばならない。
+ ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $r = 'range';
+ $w = 'array_walk';
+ $p = 'printf';
+
+ $s = $r(1, 100);
+ $w(
+ $s,
+ fn($i) =>
+ $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
+ );
+ ]]>
+ </programlisting>
+ <para>
+ これで関数を呼び出している所は短くなった。
+ では、<literal>$r</literal> や <literal>$w</literal> や <literal>$p</literal>、
+ また <literal>'Fizz'</literal> や <literal>'Buzz'</literal> はどうやって 1 行 2 文字に収めるのか。
+ 次のテクニックへ移ろう。
+ </para>
+ </section>
+ <section xml:id="commentary--incompatible-solution">
+ <title>余談: PHP 8.x で動作しなくてもいいなら</title>
+ <para>
+ 今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>PHP 7.4〜8.1 で動作すること</listitem>
+ </itemizedlist>
+ </blockquote>
+ <para>
+ というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。
+ 例えば、 <literal>Fizz</literal> という文字列が欲しければ、次のようにする。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $f
+ =F
+ .i
+ .z
+ .z
+ ;;
+ ]]>
+ </programlisting>
+ <para>
+ こうして簡単に文字列を作れる。
+ なお、この仕様は 7.x 時点でも警告を受けるので、<literal>@</literal> 演算子を使って抑制してやるとよい。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $f
+ =@
+ F.
+ @i
+ .#
+ @z
+ .#
+ @z
+ ;;
+ ]]>
+ </programlisting>
+ <para>
+ むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
+ </para>
+ </section>
+ <section xml:id="commentary--shorten-string-literals">
+ <title>文字列リテラルの短縮</title>
+ <para>
+ 実際に使った手法の説明に移る。
+ </para>
+ <para>
+ ずばり、文字列同士のビット演算を使う。
+ PHP では、文字列同士でビット演算 (<literal>&amp;</literal>、<literal>|</literal>、<literal>^</literal>) をした場合、
+ 文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $a = "12345";
+ $b = "world";
+
+ // $a ^ $b は次のコードと同じ
+ $result = '';
+ for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
+ $result .= $a[$i] ^ $b[$i];
+ }
+
+ echo $result;
+ // => F]AXQ
+ ]]>
+ </programlisting>
+ <para>
+ これを踏まえ、次のコードを見てみよう。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x = "x\nOm\n";
+ $y = "\nk!\no";
+ $r = $x ^ $y;
+ echo "$r\n";
+ ]]>
+ </programlisting>
+ <para>
+ 実行すると、<literal>range</literal> が表示される。
+ さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。
+ 書きかえてみよう。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x
+ ='x
+ Om
+ ';
+ $y
+ ='
+ k!
+ o'
+ ;
+
+ $r = $x ^ $y;
+ echo "$r\n";
+ ]]>
+ </programlisting>
+ <para>
+ さらに <literal>#</literal> を使って適当に調整すると、次のようになる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $x
+ =#
+ 'x
+ Om
+ ';
+ $y
+ ='
+ k!
+ o'
+ ;#
+ $r
+ =#
+ $x
+ ^#
+ $y
+ ;#
+
+ echo "$r\n";
+ ]]>
+ </programlisting>
+ <para>
+ 1行あたり2文字で、<literal>range</literal> という文字列を生成することに成功した。
+ 他の必要な文字列にも、同様の処理をほどこす。
+ </para>
+ <para>
+ 備考: <literal>Buzz</literal> 中にある小文字の <literal>u</literal> は、このロジックだと non-printable な文字になってしまう。
+ ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。
+ </para>
+ </section>
+ </section>
+ <section xml:id="stretched-fizzbuzz">
+ <title>完成系</title>
+ <para>
+ 完成したものがこちら。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $x
+ =#
+ 'i
+ S'
+ ;;
+ $y
+ ='
+ b!
+ ';
+ $c
+ =#
+ $x
+ ^#
+ $y
+ ;#
+ $x
+ =#
+ 'x
+ Om
+ ';
+ $y
+ ='
+ k!
+ o'
+ ;#
+ $r
+ =#
+ $x
+ ^#
+ $y
+ ;#
+ $x
+ =#
+ 'k
+ Sk
+ ~}
+ Ma
+ ';
+ $y
+ ='
+ x!
+ s!
+ k!
+ ';
+ $w
+ =#
+ $x
+ ^#
+ $y
+ ;#
+ $x
+ =#
+ 'z
+ Hd
+ G'
+ ;#
+ $y
+ ='
+ x!
+ ~!
+ ';
+ $p
+ =#
+ $x
+ ^#
+ $y
+ ;#
+ $x
+ =#
+ 'L
+ [p
+ ';
+ $y
+ ='
+ c!
+ ';
+ $f
+ =#
+ $x
+ ^#
+ $y
+ ;#
+ $x
+ =#
+ 'H
+ [p
+ ';
+ $y
+ ='
+ _!
+ ';
+ $b
+ =#
+ $x
+ ^#
+ $y
+ ;#
+ $b
+ [1
+ ]=
+ $c
+ (#
+ 13
+ *9
+ );
+ $s
+ =#
+ $r
+ (1
+ ,(
+ 10
+ **
+ 2)
+ );
+ $w
+ (#
+ $s
+ ,#
+ fn
+ (#
+ $i
+ )#
+ =>
+ $p
+ ((
+ (#
+ $i
+ %3
+ ?#
+ ''
+ :#
+ $f
+ ).
+ (#
+ $i
+ %5
+ ?#
+ ''
+ :#
+ $b
+ )?
+ :#
+ $i
+ )#
+ .'
+ ')
+ );
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="outro">
+ <title>感想など</title>
+ <para>
+ PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。
+ この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
+ </para>
+ <para>
+ みんなもプログラムを細長くしよう。
+ </para>
+ </section>
+ <section xml:id="alternative-solution">
+ <title>余談2: 別解</title>
+ <para>
+ PHP では、バッククォートを使ってシェルを呼び出せる。
+ これは <literal>shell_exec</literal> 関数と等価である。
+ さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える
+ (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ printf(`
+ e\
+ c\
+ h\
+ o\
+ \
+ 1\
+ 2\
+ 3\
+ `);
+ ]]>
+ </programlisting>
+ <para>
+ なお、ここでは簡単のため出力に <literal>printf</literal> をそのまま使っているが、
+ 実際には <literal>printf</literal> という文字列を合成して可変関数で呼び出す。
+ </para>
+ <para>
+ ただし、これでは
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>スペースやタブを使用しないこと</listitem>
+ </itemizedlist>
+ </blockquote>
+ <para>
+ に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。
+ </para>
+ <para>
+ もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $c = 'chr';
+
+ ${
+ '_
+ '}
+ =#
+ $c
+ (#
+ 32
+ ).
+ $c
+ (#
+ 92
+ );
+
+ printf(`
+ e\
+ c\
+ h\
+ o\
+ ${
+ '_
+ '}
+ 1\
+ 2\
+ 3\
+ `);
+ ]]>
+ </programlisting>
+ <para>
+ 先程と同じく、<literal>chr</literal> や <literal>printf</literal> を生成する部分は長くなるので省いた。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ ${
+ '_
+ '}
+ ]]>
+ </literallayout>
+ <para>
+ は変数で、中にはスペースとエスケープが入っている (<literal>chr(32) . chr(92)</literal>)。
+ シェルに渡されている文字列は次のようになる。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ e\
+ c\
+ h\
+ o\
+ \
+ 1\
+ 2\
+ 3\
+ ]]>
+ </literallayout>
+ <para>
+ これは、前掲したコマンドと同じだ。
+ かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。
+ Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。
+ </para>
+ <para>
+ ということでこれは別解ということにしておく。
+ </para>
+ <para>
+ ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
+ </para>
+ <literallayout class="monospaced">
+ <![CDATA[
+ ${
+ '_
+ '}
+ ]]>
+ </literallayout>
+ <para>
+ 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml
new file mode 100644
index 00000000..79202320
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2023: ボツになったトークン問題 その 1</title>
+ <abstract>
+ 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。
+ </abstract>
+ <keywordset>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-10-23</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、
+ <link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> において、
+ 昨年と同様に、弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> から、
+ トークン問題を出題予定である。
+ </para>
+ <para>
+ 昨年のトークン問題の記事はこちら: <link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens">PHPerKaigi 2022 トークン問題の解説</link>
+ </para>
+ <para>
+ すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。
+ せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+ </para>
+ <para>
+ 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
+ </para>
+ </section>
+ <section xml:id="quiz">
+ <title>問題</title>
+ <para>
+ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ $π = $argv[1] ?? null;
+ if ($π === null) {
+ exit('No input.');
+ }
+ $π = trim($π);
+ if (!is_numeric($π)) {
+ exit('Invalid input.');
+ }
+
+ $s = implode(array_map(chr(...), str_split($π, 2)));
+
+ preg_match('/(\x23.+?) /', $s, $m);
+ $t = $m[1] ?? '';
+
+ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+ echo "Token: {$t}\n";
+ } else {
+ echo "Failed.\n";
+ }
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="how-to-obtain-token">
+ <title>トークン入手方法</title>
+ <para>
+ ソースを見るとわかるとおり、<literal>$argv[1]</literal> を参照している。
+ それを <literal>$π</literal> なる変数に代入しているので、円周率を渡してみる。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ php Q.php 3.14
+ Failed.
+ ]]>
+ </programlisting>
+ <para>
+ 失敗してしまった。精度を上げてみる。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ php Q.php 3.1415
+ Failed.
+ ]]>
+ </programlisting>
+ <para>
+ だめだった。これを成功するまで繰り返す。
+ </para>
+ <para>
+ 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ php Q.php 3.1415926535897932
+ Token: #YO
+ ]]>
+ </programlisting>
+ <para>
+ めでたくトークン「<literal>#YO</literal>」が手に入った。
+ </para>
+ </section>
+ <section xml:id="commentary">
+ <title>解説</title>
+ <para>
+ 短いので頭から追っていく。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $π = $argv[1] ?? null;
+ if ($π === null) {
+ exit('No input.');
+ }
+ $π = trim($π);
+ if (!is_numeric($π)) {
+ exit('Invalid input.');
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 入力のバリデーション部分。数値のみ受け付ける。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $s = implode(array_map(chr(...), str_split($π, 2)));
+ ]]>
+ </programlisting>
+ <para>
+ <literal>$π</literal> を 2 文字ごとに区切り (<literal>str_split</literal>)、
+ 数値を ASCII コードと見做して文字に変換 (<literal>chr</literal>) して結合 (<literal>implode</literal>) している。
+ </para>
+ <para>
+ 例えば、<literal>$π</literal> が <literal>'656667'</literal> だったとすると、
+ <literal>65</literal>、<literal>66</literal>、<literal>67</literal> に対応した
+ <literal>'A'</literal>、<literal>'B'</literal>、<literal>'C'</literal> へと変換され、<literal>'ABC'</literal> になる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $π = '656667';
+ $s = implode(array_map(chr(...), str_split($π, 2)));
+ echo $s;
+ // => ABC
+ ]]>
+ </programlisting>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ preg_match('/(\x23.+?) /', $s, $m);
+ $t = $m[1] ?? '';
+ ]]>
+ </programlisting>
+ <para>
+ 正規表現でマッチングしている。<literal>\x23</literal> は <literal>#</literal> と同じであることに留意すると、
+ この正規表現は「<literal>#</literal> から始まる 2 以上の長さ (含 <literal>#</literal>) の文字列で、
+ 最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。
+ </para>
+ <para>
+ なお、<literal>#</literal> を直接書いていないのは、<literal>/#.+?) /</literal> と書くと、
+ <literal>#.+?)</literal> という意図せぬトークンが登録されてしまうからである。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+ echo "Token: {$t}\n";
+ } else {
+ echo "Failed.\n";
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 最後にトークンのハッシュ値を見て、想定解かどうかを確認する。
+ </para>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。
+ </para>
+ <para>
+ 最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた
+ (ちなみに、それでも <literal>M_PI</literal> や <literal>pi()</literal> では精度が足りない)。
+ 見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。
+ むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml
new file mode 100644
index 00000000..b0ad60e0
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>【備忘録】 このサイト用の VPS をセットアップしたときのメモ</title>
+ <abstract>
+ GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。
+ </abstract>
+ <keywordset>
+ <keyword>note-to-self</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-10-28</date>
+ <revremark>公開</revremark>
+ </revision>
+ <revision>
+ <date>2023-08-30</date>
+ <revremark>ssh_config に IdentitiesOnly yes を追加</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。
+ そのときにおこなったサーバのセットアップ作業を書き残しておく。
+ 99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。
+ </para>
+ <para>
+ 未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。
+ </para>
+ </section>
+ <section xml:id="vps">
+ <title>VPS</title>
+ <para>
+ <link xl:href="https://vps.sakura.ad.jp/">さくらの VPS</link> の 2 GB プラン。
+ そこまで真面目に選定していないので、困ったら移動するかも。
+ </para>
+ </section>
+ <section xml:id="preparation">
+ <title>事前準備</title>
+ <section xml:id="preparation--hostname">
+ <title>サーバのホスト名を決める</title>
+ <para>
+ モチベーションが上がるという効能がある。今回は藤原定家から取って <literal>teika</literal> にした。
+ たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。
+ </para>
+ </section>
+ <section xml:id="preparation--ssh-key">
+ <title>SSH の鍵生成</title>
+ <para>
+ ローカルマシンで鍵を生成する。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
+ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
+ ]]>
+ </programlisting>
+ <para>
+ <literal>teika.key</literal> はローカルからサーバへの接続用、<literal>github2teika.key</literal> は、
+ GitHub Actions からサーバへのデプロイ用。
+ </para>
+ </section>
+ <section xml:id="preparation--ssh-config">
+ <title>SSH の設定</title>
+ <para>
+ <literal>.ssh/config</literal> に設定しておく。
+ </para>
+ <programlisting language="ssh_config" linenumbering="unnumbered">
+ <![CDATA[
+ Host teika
+ HostName **********
+ User **********
+ Port **********
+ IdentityFile ~/.ssh/teika.key
+ IdentitiesOnly yes
+ ]]>
+ </programlisting>
+ </section>
+ </section>
+ <section xml:id="basic-setup">
+ <title>基本のセットアップ</title>
+ <section xml:id="basic-setup--login">
+ <title>SSH 接続</title>
+ <para>
+ VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。
+ </para>
+ </section>
+ <section xml:id="basic-setup--user">
+ <title>ユーザを作成する</title>
+ <para>
+ 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。
+ <literal>sudo</literal> グループに追加して <literal>sudo</literal> できるようにし、<literal>su</literal> で切り替え。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo adduser **********
+ $ sudo adduser ********** sudo
+ $ su **********
+ $ cd
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="basic-setup--hostname">
+ <title>ホスト名を変える</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo hostname teika
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="basic-setup--public-key">
+ <title>公開鍵を置く</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ mkdir ~/.ssh
+ $ chmod 700 ~/.ssh
+ $ vi ~/.ssh/authorized_keys
+ ]]>
+ </programlisting>
+ <para>
+ <literal>authorized_keys</literal> には、ローカルで生成した <literal>~/.ssh/teika.key.pub</literal> と
+ <literal>~/.ssh/github2teika.key.pub</literal> の内容をコピーする。
+ </para>
+ </section>
+ <section xml:id="basic-setup--ssh-config">
+ <title>SSH の設定</title>
+ <para>
+ SSH の設定を変更し、少しでも安全にしておく。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
+ $ sudo vi /etc/ssh/sshd_config
+ ]]>
+ </programlisting>
+ <itemizedlist>
+ <listitem><literal>Port</literal> を変更</listitem>
+ <listitem><literal>PermitRootLogin</literal> を <literal>no</literal> に</listitem>
+ <listitem><literal>PasswordAuthentication</literal> を <literal>no</literal> に</listitem>
+ </itemizedlist>
+ <para>
+ そして設定を反映。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo systemctl restart sshd
+ $ sudo systemctl status sshd
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="basic-setup--ssh-connect">
+ <title>SSH で接続確認</title>
+ <para>
+ 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。
+ セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ ssh teika
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="basic-setup--close-ports">
+ <title>ポートの遮断</title>
+ <para>
+ デフォルトの 22 番を閉じ、設定したポートだけ空ける。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo ufw deny ssh
+ $ sudo ufw allow *******
+ $ sudo ufw enable
+ $ sudo ufw reload
+ $ sudo ufw status
+ ]]>
+ </programlisting>
+ <para>
+ ここでもう一度 SSH の接続確認を挟む。
+ </para>
+ </section>
+ <section xml:id="basic-setup--ssh-key-for-github">
+ <title>GitHub 用の SSH 鍵</title>
+ <para>
+ GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
+ $ cat ~/.ssh/github.key.pub
+ ]]>
+ </programlisting>
+ <para>
+ <link xl:href="https://github.com/settings/ssh">GitHub の設定画面</link> から、この公開鍵を追加する。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ vi ~/.ssh/config
+ ]]>
+ </programlisting>
+ <para>
+ 設定はこう。
+ </para>
+ <programlisting language="ssh_config" linenumbering="unnumbered">
+ <![CDATA[
+ Host github.com
+ HostName github.com
+ User git
+ Port 22
+ IdentityFile ~/.ssh/github.key
+ IdentitiesOnly yes
+ ]]>
+ </programlisting>
+ <para>
+ 最後に接続できるか確認しておく。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ ssh -T github.com
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="basic-setup--upgrade-packages">
+ <title>パッケージの更新</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo apt update
+ $ sudo apt upgrade
+ $ sudo apt update
+ $ sudo apt upgrade
+ $ sudo apt autoremove
+ ]]>
+ </programlisting>
+ </section>
+ </section>
+ <section xml:id="site-hosting-setup">
+ <title>サイトホスティング用のセットアップ</title>
+ <section xml:id="site-hosting-setup--dns">
+ <title>DNS に IP アドレスを登録する</title>
+ <para>
+ このサーバは固定の IP アドレスがあるので、<literal>A</literal> レコードに直接入れるだけで済んだ。
+ </para>
+ </section>
+ <section xml:id="site-hosting-setup--install-softwares">
+ <title>使うソフトウェアのインストール</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo apt install docker docker-compose git make
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="site-hosting-setup--docker">
+ <title>メインユーザが Docker を使えるように</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo adduser ********** docker
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="site-hosting-setup--open-http-ports">
+ <title>HTTP/HTTPS を通す</title>
+ <para>
+ 80 番と 443 番を空ける。
+ </para>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ sudo ufw allow 80/tcp
+ $ sudo ufw allow 443/tcp
+ $ sudo ufw reload
+ $ sudo ufw status
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="site-hosting-setup--clone-repositories">
+ <title>リポジトリのクローン</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ cd
+ $ git clone git@github.com:nsfisis/nsfisis.dev.git
+ $ cd nsfisis.dev
+ $ git submodule update --init
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="site-hosting-setup--certbot">
+ <title>certbot で証明書取得</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ docker-compose up -d acme-challenge
+ $ make setup
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="site-hosting-setup--run-server">
+ <title>サーバを稼動させる</title>
+ <programlisting language="shell-session" linenumbering="unnumbered">
+ <![CDATA[
+ $ make serve
+ ]]>
+ </programlisting>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>感想</title>
+ <para>
+ (業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。
+ とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。
+ コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。
+ 次の式年遷宮では、手順の一部だけでも自動化したいところ。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml
new file mode 100644
index 00000000..0332179d
--- /dev/null
+++ b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2023: ボツになったトークン問題 その 2</title>
+ <abstract>
+ 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。
+ </abstract>
+ <keywordset>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2022-11-19</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> において、
+ 昨年と同様に、弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> からトークン問題を出題予定である。
+ </para>
+ <para>
+ 昨年のトークン問題の記事はこちら: <link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</link>
+ </para>
+ <para>
+ すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+ </para>
+ <para>
+ 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
+ </para>
+ <para>
+ その 1 はこちら: <link xl:href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</link>
+ </para>
+ </section>
+ <section xml:id="quiz">
+ <title>問題</title>
+ <para>
+ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ ]]>
+ </programlisting>
+ <para>
+ "And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。
+ </para>
+ </section>
+ <section xml:id="how-to-obtain-token">
+ <title>トークン入手方法</title>
+ <para>
+ 実行してみると、次のような出力が得られる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ #
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ ]]>
+ </programlisting>
+ <para>
+ 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ #
+ W
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​<?php printf((isset($s)?fn($s)=>trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ ]]>
+ </programlisting>
+ <para>
+ 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ #
+ W
+ E
+ L
+ O
+ V
+ E
+ P
+ H
+ P
+ ]]>
+ </programlisting>
+ <para>
+ トークン「#WELOVEPHP」が手に入った。
+ </para>
+ </section>
+ <section xml:id="commentary">
+ <title>解説</title>
+ <para>
+ 一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。
+ </para>
+ <para>
+ Vim で開くと次のようになる (1 行目を抜粋)。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ ]]>
+ </programlisting>
+ <para>
+ <literal>&lt;200b&gt;</literal> と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。
+ </para>
+ <note>
+ <para>
+ エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。
+ </para>
+ </note>
+ <para>
+ 文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。
+ </para>
+ <para>
+ 続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて <literal>&lt;200b&gt;</literal> と記載する。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ fn($s)=>chr(strlen($s)/3)
+ ]]>
+ </programlisting>
+ <para>
+ PHP の <literal>strlen()</literal> は文字列のバイト数を返す。1 行目の <literal>$s</literal> は以下の内容となっており、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'
+ ]]>
+ </programlisting>
+ <para>
+ このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは <literal>#</literal> の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。
+ </para>
+ <para>
+ デコード部以外の部分は、quine のための記述である。
+ </para>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ <link xl:href="https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html">CVE-2021-42574</link> に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。
+ </para>
+ <para>
+ ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml
new file mode 100644
index 00000000..90838852
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml
@@ -0,0 +1,329 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2023: ボツになったトークン問題 その 3</title>
+ <abstract>
+ 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。
+ </abstract>
+ <keywordset>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2023-01-10</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> において、
+ 昨年と同様に、弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> からトークン問題を出題予定である。
+ </para>
+ <para>
+ 昨年のトークン問題の記事はこちら: <link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</link>
+ </para>
+ <para>
+ すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。
+ せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+ </para>
+ <para>
+ 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
+ </para>
+ <itemizedlist>
+ <listitem>その 1 はこちら: <link xl:href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</link></listitem>
+ <listitem>その 2 はこちら: <link xl:href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">PHPerKaigi 2023: ボツになったトークン問題 その 2</link></listitem>
+ </itemizedlist>
+ </section>
+ <section xml:id="quiz">
+ <title>問題</title>
+ <para>
+ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+ try {
+ f(g() / __LINE__);
+ } catch (Throwable $e) {
+ while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23);
+ echo "\n";
+ }
+ function f(int $i) {
+ if ($i < 0) f();
+ try {
+ match ($i) {
+ 0 => 0 / 0,
+
+
+
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
+
+
+
+
+
+
+
+
+
+
+ 6 => 0 / 0,
+
+ 5 => 0 / 0,
+
+ 22 => 0 / 0,
+
+
+
+
+ 34, 35 => 0 / 0,
+
+
+
+
+
+
+
+
+ 25 => 0 / 0,
+ 17, 21 => 0 / 0,
+
+ 24, 32 => 0 / 0,
+
+
+
+
+
+
+
+ 33 => 0 / 0,
+
+ 16 => 0 / 0,
+
+
+ 18 => 0 / 0,
+
+
+
+
+
+
+
+
+ 7 => 0 / 0,
+
+ 2 => 0 / 0,
+ 1, 20 => 0 / 0,
+ 10, 28 => 0 / 0,
+ 8, 12, 26 => 0 / 0,
+ 4, 9, 13 => 0 / 0,
+
+
+
+
+
+ 31 => 0 / 0,
+
+ 29 => 0 / 0,
+
+ 11 => 0 / 0,
+
+
+
+ 3, 19, 23 => 0 / 0,
+
+
+ 27 => 0 / 0,
+
+ 30 => 0 / 0,
+ };
+ } finally {
+ f($i - 1);
+ }
+ }
+
+
+
+
+
+
+
+ function g() {
+ return __LINE__;
+ }
+ ]]>
+ </programlisting>
+ <para>
+ "Catchline" と名付けた作品。実行するとトークン <literal>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</literal> が得られる。
+ </para>
+ <para>
+ トークンは PHP の式になっていて、評価すると <literal>Hello, World!</literal> という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
+ </para>
+ </section>
+ <section xml:id="commentary">
+ <title>解説</title>
+ <section xml:id="commentary--summary">
+ <title>概要</title>
+ <para>
+ 例外が発生した行数にデータをエンコードし、それを <literal>catch</literal> で捕まえて表示している。
+ </para>
+ </section>
+ <section xml:id="commentary--chain-of-exceptions">
+ <title>例外オブジェクトの連鎖</title>
+ <para>
+ <link xl:href="https://www.php.net/class.Exception"><literal>Exception</literal></link> や <link xl:href="https://www.php.net/class.Error"><literal>Error</literal></link> には <literal>$previous</literal> というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:
+ </para>
+ <itemizedlist>
+ <listitem>エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める</listitem>
+ <listitem>内部エラーをラップして作られたエラーに、内部エラーの情報を含める</listitem>
+ </itemizedlist>
+ <para>
+ このうち 1つ目のケースは、 <literal>finally</literal> 節の中でエラーを投げると PHP 処理系が勝手に <literal>$previous</literal> を設定してくれる。
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+
+ try {
+ try {
+ throw new Exception("Error 1");
+ } finally {
+ throw new Exception("Error 2");
+ }
+ } catch (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+ // => Error 2
+ echo $e->getPrevious()->getMessage() . PHP_EOL;
+ // => Error 1
+ }
+ ]]>
+ </programlisting>
+ <para>
+ この知識を元に、トークンの出力部を解析してみる。
+ </para>
+ </section>
+ <section xml:id="commentary--output">
+ <title>出力部の解析</title>
+ <para>
+ 出力部をコメントや改行を追加して再掲する:
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+ try {
+ f(g() / __LINE__);
+ } catch (Throwable $e) {
+ while ($e = $e->getPrevious()) {
+ printf('%c', $e->getLine() + 23);
+ }
+ echo "\n";
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 出力をおこなう <literal>catch</literal> 節を見てみると、 <literal>Throwable::getPrevious()</literal> を呼び出してエラーチェインを辿り、 <literal>Throwable::getLine()</literal> でエラーが発生した行数を取得している。その行数に <literal>23</literal> なるマジックナンバーを足し、フォーマット指定子 <literal>%c</literal> で出力している。
+ </para>
+ <para>
+ フォーマット指定子 <literal>%c</literal> は、整数を ASCII コード<footnote>RAS syndrome</footnote> と見做して印字する。トークン <literal>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</literal> の <literal>b</literal> であれば、ASCII コード <literal>98</literal> なので、75 行目で発生したエラー、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ 1, 20 => 0 / 0,
+ ]]>
+ </programlisting>
+ <para>
+ によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。
+ </para>
+ <para>
+ それでは、エラーチェインを作る箇所、関数 <literal>f()</literal> を見ていく。
+ </para>
+ </section>
+ <section xml:id="commentary--data-construction">
+ <title>データ構成部の解析</title>
+ <para>
+ <literal>f()</literal> の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ function f(int $i) {
+ if ($i < 0) f();
+ try {
+ match ($i) {
+ 0 => 0 / 0, // 12 行目
+
+
+
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
+
+ // (略)
+
+ 30 => 0 / 0, // 97 行目
+ };
+ } finally {
+ f($i - 1);
+ }
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 前述のように、 <literal>finally</literal> 節でエラーを投げると PHP 処理系が <literal>$previous</literal> を設定する。ここでは、エラーを繋げるために <literal>f()</literal> を再帰呼び出ししている。最初に <literal>f()</literal> を呼び出している箇所を確認すると、
+ </para>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ <?php
+ try {
+ f(g() / __LINE__); // 3 行目
+ ]]>
+ </programlisting>
+ <programlisting language="php" linenumbering="unnumbered">
+ <![CDATA[
+ function g() {
+ return __LINE__; // 111 行目
+ }
+ ]]>
+ </programlisting>
+ <para>
+ <literal>f()</literal> には <literal>111 / 3</literal> で <literal>37</literal> が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら <literal>f()</literal> を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。
+ </para>
+ <para>
+ エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に
+ </para>
+ <orderedlist numeration="arabic">
+ <listitem><literal>f()</literal> の引数が足りないことによる呼び出し失敗</listitem>
+ <listitem><literal>f(0)</literal> の呼び出しで発生したゼロ除算</listitem>
+ <listitem><literal>f(1)</literal> の呼び出しで発生したゼロ除算</listitem>
+ <listitem>…</listitem>
+ <listitem><literal>f(37)</literal> の呼び出しで発生したゼロ除算</listitem>
+ </orderedlist>
+ <para>
+ となっている。出力の際は <literal>catch</literal> したエラーの <literal>getPrevious()</literal> から処理を始めるので、1 番目の <literal>f()</literal> によるエラーは無視され、 <literal>f(0)</literal> によるエラー、 <literal>f(1)</literal> によるエラー、 <literal>f(2)</literal> によるエラー、と出力が進む。
+ </para>
+ <para>
+ <literal>f()</literal> に <literal>0</literal> を渡したときは 12 行目にある <literal>match</literal> の <literal>0</literal> でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 <literal>12 + 23</literal> は <literal>35</literal>、ASCII コードでは <literal>#</literal> である。これがトークンの 1文字目にあたる。
+ </para>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 「行数」というのはトークン文字列をデコードする対象として優れている。
+ </para>
+ <itemizedlist>
+ <listitem>トークンの一部や全部が陽に現れない</listitem>
+ <listitem><literal>__LINE__</literal> で容易に取得できる</listitem>
+ </itemizedlist>
+ <para>
+ しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 <literal>__LINE__</literal> を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして <literal>Throwable</literal> を思いつき、続けてエラーオブジェクトには <literal>$previous</literal> があることを思い出した。
+ </para>
+ <para>
+ 今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で <literal>Throwable</literal> なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、<literal>0/0</literal> のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml
new file mode 100644
index 00000000..9deff792
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>このブログのジェネレータを書き直した</title>
+ <abstract>
+ このブログのジェネレータを書き直したので、やったことを書き記しておく。
+ </abstract>
+ <revhistory>
+ <revision>
+ <date>2023-03-10</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ このブログを構築するシステムを書き直したのは 2度目である。
+ 元々立ち上げた当初は、静的サイトジェネレータである <link xl:href="https://gohugo.io/">Hugo</link> を使っていた。
+ それを <link xl:href="https://asciidoctor.org/">Asciidoctor</link> にいくつかのカスタムを加えた自前のジェネレータに移行したのが 2022年の11月ごろだ。
+ そして今回、スクラッチから書いた <link xl:href="https://deno.land/">Deno</link> 製のジェネレータに移行した。
+ </para>
+ <para>
+ この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。
+ </para>
+ </section>
+ <section xml:id="from-hugo-to-asciidoctor">
+ <title>Hugo から Asciidoctor へ</title>
+ <para>
+ 最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。
+ 実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。
+ 例えば、Hugo を使っていたころはサポートしていた RSS フィードの生成は、まだ実装できていない。
+ </para>
+ <para>
+ 移行先のフォーマットとして AsciiDoc を選んだのは、Markdown よりも表現力に優れるからである。Markdown は広く使われている軽量マークアップ言語だが、以下のような欠点を持つ。
+ </para>
+ <para>
+ <itemizedlist>
+ <listitem>CommonMark では機能が貧弱である (例: 脚注、<code>id</code> 属性の付与)</listitem>
+ <listitem>拡張記法に実装間で互換性がない</listitem>
+ <listitem>メタデータ (公開日など) を埋め込む統一された方法がない</listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ AsciiDoc は Markdown に比べると普及していないが、上記の欠点は克服している。
+ </para>
+ <para>
+ <itemizedlist>
+ <listitem>ブログを書くのに十分な表現力がある</listitem>
+ <listitem>フォーマットを拡張するときの記法があらかじめ定められている</listitem>
+ <listitem>メタデータを埋め込む統一された方法がある</listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。
+ </para>
+ </section>
+ <section xml:id="from-asciidoctor-to-my-own-generator">
+ <title>Asciidoctor から自前のジェネレータへ</title>
+ <para>
+ AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、
+ <itemizedlist>
+ <listitem>表現力が高く、</listitem>
+ <listitem>文法が厳密であり、</listitem>
+ <listitem>簡単に実装できる</listitem>
+ </itemizedlist>
+ フォーマットが求められた。これに合致したのが、XML をベースとする <link xl:href="https://docbook.org/">DocBook</link> (今回使っているのは、そのサブセットである <link xl:href="https://tdg.docbook.org/tdg/sdocbook/5.1/">Simplified DocBook</link>) である。
+ </para>
+ <para>
+ 実は、AsciiDoc と DocBook はおおよそ互換性がある。AsciiDoc で書かれた文書は (ほぼ) 情報ロスなしに DocBook へ変換でき、逆もまたしかりである。
+ よって、DocBook には、AsciiDoc と同等の表現力がある。
+ </para>
+ <para>
+ XML の文法の厳密さについては、説明するまでもないだろう。また、単純な文法であることから実装が容易であり、事実上 Asciidoctor へロックインされる AsciiDoc とは異なり、さまざまな言語で多くのライブラリが存在する。
+ </para>
+ <para>
+ 今回は、XML のパース自体も自分で書いている (これは何となく書きたかったからであり、合理的な理由があるわけではない。実装はサボりまくっているので XML のコメントが使えないといった制限がある)。
+ </para>
+ <para>
+ XML という機械処理しやすいフォーマットを選ぶことには、機械的な変換や検査といった処理がおこないやすくなるといった利点もある。
+ 欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。
+ 結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。
+ </para>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。
+ 今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。
+ </para>
+ <para>
+ 最後にもう一度書くのだが、Hugo は大変に優れた静的サイトジェネレータである。
+ 無駄な拘りがなければこれを使うとよい。
+ 私は無駄に拘ったので、ブログの記事を書く時間を潰してブログシステムを作ってしまった。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml
new file mode 100644
index 00000000..67dfad87
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml
@@ -0,0 +1,607 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PNG 画像の最小構成エンコーダを実装する</title>
+ <abstract>
+ PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。
+ </abstract>
+ <revhistory>
+ <revision>
+ <date>2023-04-01</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。
+ PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。
+ また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。
+ </para>
+ </section>
+ <section xml:id="basic-structure-of-png">
+ <title>PNG ファイルの基本構造</title>
+ <para>
+ PNG ファイルの基本構造は次のようになっている。
+ </para>
+ <orderedlist>
+ <listitem>PNG signature</listitem>
+ <listitem>IHDR chunk</listitem>
+ <listitem>任意個の chunk</listitem>
+ <listitem>IEND chunk</listitem>
+ </orderedlist>
+ <para>
+ Chunk には画像データを入れる IDAT chunk、パレットデータを入れる PLTE chunk、テキストデータを入れる tEXt chunk などがあるが、
+ 今回は最小構成ということで IDAT chunk (と IHDR chunk と IEND chunk) のみを用いる。
+ </para>
+ <para>
+ 次節で、それぞれの具体的な構造を確認しつつ実装していく。
+ </para>
+ </section>
+ <section xml:id="implement-png-encoder">
+ <title>PNG のエンコーダを実装する</title>
+ <para>
+ 以下のソースコードをベースにする。
+ 今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ <literal>image/png</literal> を用いる。
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ package main
+
+ import (
+ "image"
+ _ "image/png"
+ "io"
+ "os"
+ )
+
+ func main() {
+ inFile, err := os.Open("input.png")
+ if err != nil {
+ panic(err)
+ }
+ defer inFile.Close()
+
+ img, _, err := image.Decode(inFile)
+ if err != nil {
+ panic(err)
+ }
+
+ outFile, err := os.Create("output.png")
+ if err != nil {
+ panic(err)
+ }
+ defer outFile.Close()
+
+ writePng(outFile, img)
+ }
+
+ func writePng(w io.Writer, img image.Image) {
+ width := uint32(img.Bounds().Dx())
+ height := uint32(img.Bounds().Dy())
+ writeSignature(w)
+ writeChunkIhdr(w, width, height)
+ writeChunkIdat(w, width, height, img)
+ writeChunkIend(w)
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 以降は、<literal>writeSignature</literal> や <literal>writeChunkIhdr</literal> などを実装していく。
+ </para>
+ <section xml:id="implement-png-encoder--png-signature">
+ <title>PNG signature</title>
+ <para>
+ PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。
+ </para>
+ <orderedlist>
+ <listitem>0x89</listitem>
+ <listitem>0x50 (ASCII コードで「P」)</listitem>
+ <listitem>0x4E (ASCII コードで「N」)</listitem>
+ <listitem>0x47 (ASCII コードで「G」)</listitem>
+ <listitem>0x0D (ASCII コードで CR)</listitem>
+ <listitem>0x0A (ASCII コードで LF)</listitem>
+ <listitem>0x1A (ASCII コードで EOF)</listitem>
+ <listitem>0x0A (ASCII コードで LF)</listitem>
+ </orderedlist>
+ <para>
+ CRLF や LF は、送信中に改行コードの変換が誤っておこなわれていないかどうかを検知するのに使われる。
+ </para>
+ <para>
+ <literal>writeSignature</literal> の実装はこちら:
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ import "encoding/binary"
+
+ func writeSignature(w io.Writer) {
+ sig := [8]uint8{
+ 0x89,
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // CR
+ 0x0A, // LF
+ 0x1A, // EOF (^Z)
+ 0x0A, // LF
+ }
+ binary.Write(w, binary.BigEndian, sig)
+ }
+ ]]>
+ </programlisting>
+ <para>
+ <literal>encoding/binary</literal> パッケージの <literal>binary.Write</literal> を使い、固定の 8 バイトを書き込む。
+ </para>
+ </section>
+ <section xml:id="implement-png-encoder--structure-of-chunk">
+ <title>Chunk の構造</title>
+ <para>
+ IHDR chunk に進む前に、chunk 一般の構造を確認する。
+ </para>
+ <orderedlist>
+ <listitem>Length: chunk data のバイト長 (符号なし 4 バイト整数)</listitem>
+ <listitem>Chunk type: chunk の種類を示す 4 バイトからなる名前</listitem>
+ <listitem>Chunk data: 実際のデータ。0 バイトでもよい</listitem>
+ <listitem>CRC: chunk type と chunk data の CRC (符号なし 4 バイト整数)</listitem>
+ </orderedlist>
+ <para>
+ CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では <literal>hash/crc32</literal> パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている (<link xl:href="https://www.w3.org/TR/png/#D-CRCAppendix">D. Sample CRC implementation</link>) ので、これを Go に移植する。
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ var (
+ crcTable [256]uint32
+ crcTableComputed bool
+ )
+
+ func makeCrcTable() {
+ for n := 0; n < 256; n++ {
+ c := uint32(n)
+ for k := 0; k < 8; k++ {
+ if (c & 1) != 0 {
+ c = 0xEDB88320 ^ (c >> 1)
+ } else {
+ c = c >> 1
+ }
+ }
+ crcTable[n] = c
+ }
+ crcTableComputed = true
+ }
+
+ func updateCrc(crc uint32, buf []byte) uint32 {
+ if !crcTableComputed {
+ makeCrcTable()
+ }
+
+ c := crc
+ for n := 0; n < len(buf); n++ {
+ c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+ }
+ return c
+ }
+
+ func crc(buf []byte) uint32 {
+ return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+ }
+ ]]>
+ </programlisting>
+ <para>
+ できた <literal>crc</literal> 関数を使って、chunk 一般を書き込む関数も用意しておこう。
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ func writeChunk(w io.Writer, chunkType string, data []byte) {
+ typeAndData := make([]byte, 0, len(chunkType)+len(data))
+ typeAndData = append(typeAndData, []byte(chunkType)...)
+ typeAndData = append(typeAndData, data...)
+
+ binary.Write(w, binary.BigEndian, uint32(len(data)))
+ binary.Write(w, binary.BigEndian, typeAndData)
+ binary.Write(w, binary.BigEndian, crc(typeAndData))
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 仕様どおり、<literal>chunkType</literal> と <literal>data</literal> から CRC を計算し、<literal>data</literal> の長さと合わせて書き込んでいる。
+ PNG では基本的に big endian を使うことに注意する。
+ </para>
+ <para>
+ 準備ができたところで、具体的な chunk をエンコードしていく。
+ </para>
+ </section>
+ <section xml:id="implement-png-encoder--ihdr-chunk">
+ <title>IHDR chunk</title>
+ <para>
+ IHDR chunk は最初に配置される chunk である。次のようなデータからなる。
+ </para>
+ <orderedlist>
+ <listitem>画像の幅 (符号なし 4 バイト整数)</listitem>
+ <listitem>画像の高さ (符号なし 4 バイト整数)</listitem>
+ <listitem>
+ ビット深度 (符号なし 1 バイト整数)
+ <itemizedlist>
+ <listitem>1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ 色タイプ (符号なし 1 バイト整数)
+ <itemizedlist>
+ <listitem>0: グレースケール</listitem>
+ <listitem>2: Truecolor (今回はこれに決め打ち)</listitem>
+ <listitem>3: パレットのインデックス</listitem>
+ <listitem>4: グレースケール + アルファ</listitem>
+ <listitem>6: Truecolor + アルファ</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ 圧縮方式 (符号なし 1 バイト整数)
+ <itemizedlist>
+ PNG の仕様書に 0 しか定義されていないので 0 で固定
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ フィルタ方式 (符号なし 1 バイト整数)
+ <itemizedlist>
+ PNG の仕様書に 0 しか定義されていないので 0 で固定
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ インターレース方式 (符号なし 1 バイト整数)
+ <itemizedlist>
+ 今回はインターレースしないので 0
+ </itemizedlist>
+ </listitem>
+ </orderedlist>
+ <para>
+ 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ import "bytes"
+
+ func writeChunkIhdr(w io.Writer, width, height uint32) {
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.BigEndian, width)
+ binary.Write(&buf, binary.BigEndian, height)
+ binary.Write(&buf, binary.BigEndian, uint8(8))
+ binary.Write(&buf, binary.BigEndian, uint8(2))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+
+ writeChunk(w, "IHDR", buf.Bytes())
+ }
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="implement-png-encoder--idat-chunk">
+ <title>IDAT chunk</title>
+ <para>
+ IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。
+ </para>
+ <section xml:id="implement-png-encoder--idat-chunk--zlib">
+ <title>Zlib</title>
+ <para>
+ まずは zlib について確認する。おおよそ次のような構造になっている。
+ </para>
+ <orderedlist>
+ <listitem>固定で 0x78 (符号なし 1 バイト整数)</listitem>
+ <listitem>固定で 0x01 (符号なし 1 バイト整数)</listitem>
+ <listitem>データ</listitem>
+ <listitem>データの Adler-32</listitem>
+ </orderedlist>
+ <para>
+ 最初の 2 バイトにも意味はあるが、PNG では固定で構わない。
+ </para>
+ <para>
+ Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている (<link xl:href="https://www.rfc-editor.org/rfc/rfc1950#section-9">9. Appendix: Sample code</link>) ので、Go に移植する。
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ const adler32Base = 65521
+
+ func updateAdler32(adler uint32, buf []byte) uint32 {
+ s1 := adler & 0xFFFF
+ s2 := (adler >> 16) & 0xFFFF
+
+ for n := 0; n < len(buf); n++ {
+ s1 = (s1 + uint32(buf[n])) % adler32Base
+ s2 = (s2 + s1) % adler32Base
+ }
+ return (s2 << 16) + s1
+ }
+
+ func adler32(buf []byte) uint32 {
+ return updateAdler32(1, buf)
+ }
+ ]]>
+ </programlisting>
+ <para>
+ 「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。
+ </para>
+ <para>
+ 1 つの無圧縮ブロックには 65535 (2<superscript>16</superscript> - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
+ </para>
+ <orderedlist>
+ <listitem>最終ブロックなら 1、そうでなければ 0 (符号なし 1 バイト整数)</listitem>
+ <listitem>ブロックのバイト長 (符号なし 2 バイト整数)</listitem>
+ <listitem>ブロックのバイト長の 1 の補数、あるいはビット反転 (符号なし 2 バイト整数)</listitem>
+ <listitem>データ (最大 65535 バイト)</listitem>
+ </orderedlist>
+ <para>
+ 実際にこの手抜き zlib を実装したものがこちら:
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ func encodeZlib(data []byte) []byte {
+ var buf bytes.Buffer
+
+ binary.Write(&buf, binary.BigEndian, uint8(0x78))
+ binary.Write(&buf, binary.BigEndian, uint8(0x01))
+ blockSize := 65535
+ isFinalBlock := false
+ for i := 0; !isFinalBlock; i++ {
+ var block []byte
+ if len(data) <= (i+1)*blockSize {
+ block = data[i*blockSize:]
+ isFinalBlock = true
+ } else {
+ block = data[i*blockSize : (i+1)*blockSize]
+ }
+ binary.Write(&buf, binary.BigEndian, isFinalBlock)
+ binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+ binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+ binary.Write(&buf, binary.LittleEndian, block)
+ }
+ binary.Write(&buf, binary.BigEndian, adler32(data))
+
+ return buf.Bytes()
+ }
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="implement-png-encoder--idat-chunk--image-data">
+ <title>画像データ</title>
+ <para>
+ では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。
+ 画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。
+ 右下のピクセルまで行けば終わり。要は Z 字型に進んでいく。
+ </para>
+ <para>
+ また、それぞれの行の先頭には、圧縮のためのフィルタタイプを指定する。
+ ただ、今回はその実装を省略するために、常にフィルタ 0 (何も加工しない) を使う。
+ </para>
+ <para>
+ 先ほどの <literal>encodeZlib</literal> も使って実際に実装したものがこちら:
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+ var pixels bytes.Buffer
+ for y := uint32(0); y < height; y++ {
+ binary.Write(&pixels, binary.BigEndian, uint8(0))
+ for x := uint32(0); x < width; x++ {
+ r, g, b, _ := img.At(int(x), int(y)).RGBA()
+ binary.Write(&pixels, binary.BigEndian, uint8(r))
+ binary.Write(&pixels, binary.BigEndian, uint8(g))
+ binary.Write(&pixels, binary.BigEndian, uint8(b))
+ }
+ }
+
+ writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+ }
+ ]]>
+ </programlisting>
+ </section>
+ </section>
+ <section xml:id="implement-png-encoder--iend-chunk">
+ <title>IEND chunk</title>
+ <para>
+ 最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。
+ </para>
+ <para>
+ 特に追加のデータはなく、必要なのは chunk type の <literal>IEND</literal> くらいなので実装は簡単:
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+ }
+ ]]>
+ </programlisting>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 最後に全ソースコードを再掲しておく。
+ </para>
+ <programlisting language="go" linenumbering="unnumbered">
+ <![CDATA[
+ package main
+
+ import (
+ "bytes"
+ "encoding/binary"
+ "image"
+ _ "image/png"
+ "io"
+ "os"
+ )
+
+ func main() {
+ inFile, err := os.Open("input.png")
+ if err != nil {
+ panic(err)
+ }
+ defer inFile.Close()
+
+ img, _, err := image.Decode(inFile)
+ if err != nil {
+ panic(err)
+ }
+
+ outFile, err := os.Create("output.png")
+ if err != nil {
+ panic(err)
+ }
+ defer outFile.Close()
+
+ writePng(outFile, img)
+ }
+
+ func writePng(w io.Writer, img image.Image) {
+ width := uint32(img.Bounds().Dx())
+ height := uint32(img.Bounds().Dy())
+ writeSignature(w)
+ writeChunkIhdr(w, width, height)
+ writeChunkIdat(w, width, height, img)
+ writeChunkIend(w)
+ }
+
+ func writeSignature(w io.Writer) {
+ sig := [8]uint8{
+ 0x89,
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // CR
+ 0x0A, // LF
+ 0x1A, // EOF (^Z)
+ 0x0A, // LF
+ }
+ binary.Write(w, binary.BigEndian, sig)
+ }
+
+ func writeChunkIhdr(w io.Writer, width, height uint32) {
+ var buf bytes.Buffer
+ binary.Write(&buf, binary.BigEndian, width)
+ binary.Write(&buf, binary.BigEndian, height)
+ binary.Write(&buf, binary.BigEndian, uint8(8))
+ binary.Write(&buf, binary.BigEndian, uint8(2))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+ binary.Write(&buf, binary.BigEndian, uint8(0))
+
+ writeChunk(w, "IHDR", buf.Bytes())
+ }
+
+ func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+ var pixels bytes.Buffer
+ for y := uint32(0); y < height; y++ {
+ binary.Write(&pixels, binary.BigEndian, uint8(0))
+ for x := uint32(0); x < width; x++ {
+ r, g, b, _ := img.At(int(x), int(y)).RGBA()
+ binary.Write(&pixels, binary.BigEndian, uint8(r))
+ binary.Write(&pixels, binary.BigEndian, uint8(g))
+ binary.Write(&pixels, binary.BigEndian, uint8(b))
+ }
+ }
+
+ writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+ }
+
+ func encodeZlib(data []byte) []byte {
+ var buf bytes.Buffer
+
+ binary.Write(&buf, binary.BigEndian, uint8(0x78))
+ binary.Write(&buf, binary.BigEndian, uint8(0x01))
+ blockSize := 65535
+ isFinalBlock := false
+ for i := 0; !isFinalBlock; i++ {
+ var block []byte
+ if len(data) <= (i+1)*blockSize {
+ block = data[i*blockSize:]
+ isFinalBlock = true
+ } else {
+ block = data[i*blockSize : (i+1)*blockSize]
+ }
+ binary.Write(&buf, binary.BigEndian, isFinalBlock)
+ binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+ binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+ binary.Write(&buf, binary.LittleEndian, block)
+ }
+ binary.Write(&buf, binary.BigEndian, adler32(data))
+
+ return buf.Bytes()
+ }
+
+ func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+ }
+
+ func writeChunk(w io.Writer, chunkType string, data []byte) {
+ typeAndData := make([]byte, 0, len(chunkType)+len(data))
+ typeAndData = append(typeAndData, []byte(chunkType)...)
+ typeAndData = append(typeAndData, data...)
+
+ binary.Write(w, binary.BigEndian, uint32(len(data)))
+ binary.Write(w, binary.BigEndian, typeAndData)
+ binary.Write(w, binary.BigEndian, crc(typeAndData))
+ }
+
+ var (
+ crcTable [256]uint32
+ crcTableComputed bool
+ )
+
+ func makeCrcTable() {
+ for n := 0; n < 256; n++ {
+ c := uint32(n)
+ for k := 0; k < 8; k++ {
+ if (c & 1) != 0 {
+ c = 0xEDB88320 ^ (c >> 1)
+ } else {
+ c = c >> 1
+ }
+ }
+ crcTable[n] = c
+ }
+ crcTableComputed = true
+ }
+
+ func updateCrc(crc uint32, buf []byte) uint32 {
+ if !crcTableComputed {
+ makeCrcTable()
+ }
+
+ c := crc
+ for n := 0; n < len(buf); n++ {
+ c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+ }
+ return c
+ }
+
+ func crc(buf []byte) uint32 {
+ return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+ }
+
+ const adler32Base = 65521
+
+ func updateAdler32(adler uint32, buf []byte) uint32 {
+ s1 := adler & 0xFFFF
+ s2 := (adler >> 16) & 0xFFFF
+
+ for n := 0; n < len(buf); n++ {
+ s1 = (s1 + uint32(buf[n])) % adler32Base
+ s2 = (s2 + s1) % adler32Base
+ }
+ return (s2 << 16) + s1
+ }
+
+ func adler32(buf []byte) uint32 {
+ return updateAdler32(1, buf)
+ }
+ ]]>
+ </programlisting>
+ </section>
+ <section xml:id="references">
+ <title>参考</title>
+ <itemizedlist>
+ <listitem><link xl:href="https://www.w3.org/TR/png">Portable Network Graphics (PNG) Specification (Third Edition)</link></listitem>
+ <listitem><link xl:href="https://www.rfc-editor.org/rfc/rfc1950">ZLIB Compressed Data Format Specification version 3.3</link></listitem>
+ </itemizedlist>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml
new file mode 100644
index 00000000..18bc96ce
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHPerKaigi 2023 参加レポ</title>
+ <abstract>
+ 2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。
+ </abstract>
+ <keywordset>
+ <keyword>conference</keyword>
+ <keyword>php</keyword>
+ <keyword>phperkaigi</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2023-04-04</date>
+ <revremark>公開</revremark>
+ </revision>
+ <revision>
+ <date>2023-06-28</date>
+ <revremark>トークセッションの記事版の執筆を中止</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 2023-03-23 から 2023-03-25 にかけて開催された、<link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> に参加した。
+ 今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。
+ </para>
+ <para>
+ 昨年、一昨年の参加レポはこちら:
+ </para>
+ <itemizedlist>
+ <listitem><link xl:href="/posts/2022-05-01/phperkaigi-2022/">PHPerKaigi 2022</link></listitem>
+ <listitem><link xl:href="/posts/2021-03-30/phperkaigi-2021/">PHPerKaigi 2021</link></listitem>
+ </itemizedlist>
+ </section>
+ <section xml:id="as-speaker">
+ <title>スピーカーとして</title>
+ <para>
+ これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。
+ </para>
+ <itemizedlist>
+ <listitem>
+ 詳説「参照」:PHP 処理系の実装から参照を理解する
+ <itemizedlist>
+ <listitem><link xl:href="https://fortee.jp/phperkaigi-2023/proposal/95e4dd94-5fc7-40fe-9e1a-230e36404cbe">プロポーザル</link></listitem>
+ <listitem><link xl:href="/slides/2023-03-24/phperkaigi-2023/">スライド</link></listitem>
+ <listitem>解説記事 (執筆中) → 追記: 記事版の執筆は諦めた</listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ PHPerチャレンジ解説セッション - デジタルサーカス株式会社
+ <itemizedlist>
+ <listitem><link xl:href="https://fortee.jp/phperkaigi-2023/proposal/524c9dca-1d70-4b32-a939-9c73ffe5cb48">プロポーザル</link></listitem>
+ <listitem><link xl:href="/slides/2023-03-25/phperkaigi-2023-tokens/">スライド</link></listitem>
+ <listitem>解説記事 (執筆中) → 追記: 記事版の執筆は諦めた</listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ <para>
+ PHPer チャレンジの話については後述する。
+ 参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。
+ </para>
+ </section>
+ <section xml:id="as-staff">
+ <title>当日スタッフとして</title>
+ <para>
+ 今回はスピーカーのみならず当日スタッフとしても参加した。
+ カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。
+ </para>
+ <para>
+ PHPerKaigi は一般参加者の目線でもよくできたカンファレンスだなあという印象だったのだが、よりその思いを強くした。
+ なんとスタッフにとってもよくできたカンファレンスなのである。
+ </para>
+ <para>
+ 反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。
+ </para>
+ </section>
+ <section xml:id="as-attendee">
+ <title>参加者として</title>
+ <section xml:id="as-attendee--recommended-sessions">
+ <title>おすすめセッション</title>
+ <para>
+ 5つのセッションを厳選した。
+ </para>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2023/proposal/f7f2f18a-e6b0-47e4-ade0-e324f72428ae">ブラウザの向こう側で「200 OK」を返すまでに何が起きているのか調べてみた</link>
+ </para>
+ <para>
+ Web に関わるなら、バックエンドでもフロントエンドでも知っておいてほしい知識。
+ タイトルを見て「こんな話だろうな」と想像がつくレベルなら見なくてもいいかも。
+ </para>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2023/proposal/280706e0-7158-4237-8202-c9d64330b96f">PHPで学ぶ "Cacheの距離" の話</link>
+ </para>
+ <para>
+ これも上セッションと同様に、基礎を抑えられる良いセッション。
+ </para>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2023/proposal/ad3ba31c-0214-4557-a0df-3755db8ed8cc">防衛的 PHP: 多様性を生き抜くための PHP 入門</link>
+ </para>
+ <para>
+ 静的解析ツールの話。静的解析は PHP のみならず最近の動的言語の一大潮流なので、逃れられない。
+ </para>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2023/proposal/e00788a4-ef25-49ee-b254-9d2b53e19633">PHPの最高機能、配列を捨てよう!!</link>
+ </para>
+ <para>
+ 実はこれも上のセッションと同様の話。
+ PHP の静的解析ツールは配列にも (無理矢理) 型が付けられるものが多いが、実行時にも検査できるという点において専用のクラスを作る方が優れている。
+ </para>
+ <para>
+ <link xl:href="https://fortee.jp/phperkaigi-2023/proposal/7e212cb2-be37-43e8-b6ee-5236d259fcbf">時間を気にせず普通にカンニングもしつつ ISUCON12 本選問題を PHP でやってみる</link>
+ </para>
+ <para>
+ 個人的に最も楽しみにしていたセッションであり、今回のモリアガリトーク賞 (盛り上がったセッションに運営側から贈られる賞) でもある。
+ ネタバレになるが、最終的に (Go で実装された) 本戦優勝スコアを超えている。
+ </para>
+ </section>
+ <section xml:id="as-attendee--phper-challenge">
+ <title>PHPer チャレンジ</title>
+ <para>
+ 昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。
+ また、今年はさらに作成した問題を解説するセッションにも登壇した。
+ 今年のトークンは、昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作した。
+ </para>
+ <para>
+ 問題の制作中は大変楽しかったが、まあやりすぎた。
+ いかに超絶技巧を凝らすかに注力してしまい、解く楽しさという観点を失ってしまったきらいがある。
+ </para>
+ <para>
+ (WIP: 解説ブログ記事執筆中。終わったらここにリンク)
+ </para>
+ </section>
+ <section xml:id="as-attendee--random-thoughts">
+ <title>雑多な感想</title>
+ <para>
+ なんかいろいろ。
+ </para>
+ <itemizedlist>
+ <listitem>マカロンおいしかった</listitem>
+ <listitem>\ペチパー/</listitem>
+ <listitem>名札便利</listitem>
+ <listitem>\ペチパー/</listitem>
+ <listitem>傘袋便利</listitem>
+ <listitem>\ペチパー/</listitem>
+ <listitem>パーカーのデザイン良き</listitem>
+ </itemizedlist>
+ <para>
+ (あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった)
+ </para>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ <link xl:href="/posts/2022-05-01/phperkaigi-2022/#section--next-year">去年の参加レポ</link>では、来年の目標として次を挙げた。
+ </para>
+ <blockquote>
+ <itemizedlist>
+ <listitem>プロポーザルを出す</listitem>
+ <listitem>PHPer チャレンジのトークン問題を 5題作成する</listitem>
+ <listitem>現地に行く</listitem>
+ <listitem>PHPer チャレンジで圧勝する</listitem>
+ </itemizedlist>
+ </blockquote>
+ <para>
+ プロポーザルに関しては採択されて登壇できたし、PHPer チャレンジは解説もおこなった。また、現地に行くだけでなく、当日スタッフとして参加した。
+ 4つ目の PHPer チャレンジに関しては、今年は参加していない。
+ スタッフをやりながらだと入力する時間も探す時間も取れそうになかったのと、スタッフをやっている関係で少しだけ早く入手してしまうトークンがいくつか存在していたため。
+ </para>
+ <para>
+ カンファレンス全体の感想についてだが、大規模なカンファレンスにオフラインで参加するのは今回が初めてだったので、その話をしたい。
+ </para>
+ <para>
+ オンラインとオフラインだと体験が別物になる。そもそもが似て非なるものなのだ。
+ 向き不向きはあるだろうが、オンラインしか参加したことのないという方は、一度現地参加してみてはいかがだろうか。
+ </para>
+ <para>
+ さて、参加レポは去年も一昨年もこの言葉で締め括っているので、今年もそれで終わろうと思う。
+ </para>
+ <para>
+ ではまた来年。
+ </para>
+ </section>
+</article>
diff --git a/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml
new file mode 100644
index 00000000..d713773e
--- /dev/null
+++ b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0">
+ <info>
+ <title>PHP カンファレンス福岡 2023 参加レポ</title>
+ <abstract>
+ 2023-06-24 に開催された、PHP カンファレンス福岡に参加した。
+ </abstract>
+ <keywordset>
+ <keyword>conference</keyword>
+ <keyword>php</keyword>
+ <keyword>phpconfuk</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2023-06-25</date>
+ <revremark>公開</revremark>
+ </revision>
+ </revhistory>
+ </info>
+ <section xml:id="intro">
+ <title>はじめに</title>
+ <para>
+ 2023-06-24 に開催された、<link xl:href="https://phpcon.fukuoka.jp/2023/">PHP カンファレンス福岡 2023</link>に参加した。
+ また、その前日に催された、<link xl:href="https://connpass.com/event/282285/">非公式の前夜祭</link>にも参加した。
+ 前夜祭では、15分の登壇もおこなった。<link xl:href="/slides/2023-06-23/phpconfuk-2023-eve/">登壇の方の資料はこちら。</link>
+ </para>
+ </section>
+ <section xml:id="sessions-thoughts">
+ <title>セッションの感想</title>
+ <section xml:id="sessions-thoughts--eve">
+ <title>前夜祭</title>
+ <para>
+ ※セッションの題名と発表者名は、<link xl:href="https://connpass.com/event/282285/">前夜祭イベントの connpass ページ</link>から引用。
+ </para>
+ <itemizedlist>
+ <listitem>
+ スクラム(の一部)を導入してよくなったこと (asumikam さん)
+ <itemizedlist>
+ <listitem>
+ スクラムの「一部」を導入されたということでしたが、理想的な形で改善が進んでいるように見受けられました。
+ 特に、ブランチ運用やデプロイ頻度、フィードバックサイクルに大きく変化が起きているのは驚くべき成果だと感じました。
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ 地方の小さな勉強会を一番の活動舞台にする (tomio さん)
+ <itemizedlist>
+ <listitem>
+ すさまじいほどの「熱」を感じました。
+ 私自身、最近になってカンファレンスや勉強会への参加・登壇を活発におこなうようになったことで、頷く点が多かったです。
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ </section>
+ <section xml:id="sessions-thoughts--conference">
+ <title>カンファレンス</title>
+ <para>
+ ※セッションの題名と発表者名は、<link xl:href="https://fortee.jp/phpconfukuoka-2023/proposal/accepted">カンファレンスの fortee ページ</link>から引用。
+ </para>
+ <itemizedlist>
+ <listitem>
+ <link xl:href="https://fortee.jp/phpconfukuoka-2023/proposal/df5f06e8-900e-4e71-94d7-d0c3cc57a0ac">育成力 - エンジニアの才能を引き出す環境とチューターの立ち回り - (岡嵜 雄平 さん)</link>
+ <itemizedlist>
+ <listitem>
+ ちょうど弊チームに新規メンバがジョインしたばかりで、オンボーディングプロセスについて考えていたところの発表でした。
+ すぐにすべてを取り入れるというわけにはいきませんが、弊社での新人育成プロセスの改善につながるヒントをいくつか得られたと思います。
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ オブジェクト指向は本当に必要か? (たなかひさてる さん、こいほげ さん)
+ <itemizedlist>
+ <listitem>
+ ※当日 D ホールでおこなわれたアンカンファレンスセッションのため、正式タイトル・リンクなし
+ </listitem>
+ <listitem>
+ 私自身、「オブジェクト指向」については色々と言いたいことがあるのですが、だいたいツイートしたこれとこれです。
+ <itemizedlist>
+ <listitem>
+ 「オブジェクト指向の話は、パラダイムの異なる複数の言語に触れているかどうかで見え方がまったく異なる印象がある。OOPはどうでもいいです (※個人の感想です)」 (<link xl:href="https://twitter.com/nsfisis/status/1672502935983656960">Twitter のツイートへのリンク</link>)
+ </listitem>
+ <listitem>
+ 「OOPは現代の言語で考える意味はほぼない古いパラダイムだよという立場ですが、OOPについてあまり大っぴらに話してると色んなところから刺されそうなんですよね (Twitterは大っぴらじゃないんですか?)」 (<link xl:href="https://twitter.com/nsfisis/status/1672504892244787201">Twitter のツイートへのリンク</link>)
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ <listitem>
+ <link xl:href="https://fortee.jp/phpconfukuoka-2023/proposal/ae71f3a7-4c3c-4c87-8816-8426bcc8d325">その説明、コードコメントに書く?コミットメッセージに書く?プルリクエストに書く? (おかしょい/岡田正平 さん)</link>
+ <itemizedlist>
+ <listitem>
+ Twitter にもツイートしましたが、完全に自分の意見と一致していたので、とても共感できました。
+ 今後は社内のコードレビュー時に、こちらの資料を貼りつけることにします。
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </itemizedlist>
+ </section>
+ </section>
+ <section xml:id="outro">
+ <title>おわりに</title>
+ <para>
+ 居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。
+ 受け取った「熱」が冷める前に、自らの手を動かしていきたい。
+ </para>
+ </section>
+</article>