Laravel 11へのバージョンアップでマイグレーションが失敗する

Laravel 11で、カラム変更時のchange()メソッドの挙動が変わりました。

既存のマイグレーションはエラーなく通ってしまうため、意図しないスキーマが気づかないうちに作られるのが厄介です。

変更点

change()は、定義に明示した属性のみを適用し、省略した属性は削除するようになりました。

維持したい属性はすべて書き直す必要があります。

// Laravel 11以降:維持する属性をすべて明示する
$table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();

unsigned()default(1)を省けば、その属性は失われます。

背景

Laravel 10以前はdoctrine/dbalに依存し、変更時に既存のカラム定義をDBから読み取って差分を適用していました。
省略した属性が維持されて見えたのはこのためです。

Laravel 11ではdoctrine/dbal依存が廃止され、ネイティブのスキーマ操作に移行しました。
change()は与えられた定義をそのまま新しい状態として適用するため、明示しない属性は引き継がれません。

実害が出やすいケース

旧挙動を前提に書かれたchange()を、Laravel 11環境でロールバック・再適用するケースです。

// 旧挙動では unsigned / default が維持される前提
$table->integer('votes')->change();

これを再適用すると属性が欠落し、意図と異なるスキーマになります。

マイグレーション自体は成功扱いで終わるため、気づきにくいのが問題です。

バージョンアップ時は、既存のchange()呼び出しを洗い出し、維持すべき属性が明示されているか確認してください。

参考: Laravel 11 マイグレーションドキュメント

Claude Code の Backlog 投稿を、Markdown 経由で正しい記法に変換する

課題

Claude Code に Backlog へコメントや課題を投稿させると、存在しない Backlog 記法を出力してしまうことがありました。

アプローチ

Claude Code は Markdown を正確に扱えます。
そこで、記法を直接生成させるのではなく、いったん Markdown で内容を作成し、それを Backlog 記法へ機械的に変換する方針を取りました。

この変換を担うのが Md2Backlog です。
Claude Code が Markdown で書いた内容をこのプログラムに通すことで、正しい Backlog 記法として投稿できるようになりました。

結果

記法の誤りに悩まされることがなくなり、Claude Code を Backlog 連携のワークフローに安心して組み込めるようになりました。

変換プログラム(Md2Backlog): https://github.com/gesource/Md2Backlog

PHPからMySQLに4バイト文字を保存できない問題

環境 PHP 8.2 / MySQL 8.4

PHP製Webアプリで、特定の文字列のINSERTが Incorrect string value で失敗しました。

原因

失敗するのは4バイトのUTF-8文字(絵文字、BMP外の漢字など)を含むケースでした。DBが utf8mb3 で構築されており、3バイトまでしか格納できないためです。MySQL 8.0以降のサーバーデフォルトは utf8mb4 ですが、既存DBは旧設定のまま残っていました。

解決の要点

文字セットは接続・DB・テーブル/カラムの全レイヤーで utf8mb4 に揃えます。ALTER DATABASE は既存テーブルを変換しないため、実データを持つテーブル・カラムの変換が必須です。ここを忘れるとカラムが utf8mb3 のまま残り、再発します。

接続側も utf8mb4 を指定します。照合順序を明示する場合は、8.0以降のデフォルトである utf8mb4_0900_ai_ci をテーブルと揃えておきます。

結論

デフォルト値や推奨設定はバージョンで変わります。ミドルウェアを更新するときは、設定パラメータの見直しもあわせて行いましょう。

HomebrewのOpenJDKで jpackage が動かない理由

きっかけ

ある日、Java アプリケーションの macOS 向け配布パッケージを jpackage で作成しようとしたところ、エラーで処理が止まりました。

調べた結果、JAVA_HOME に指定していたのが Homebrew でインストールした OpenJDK だったことが根本原因でした。
Temurin に切り替えた途端、エラーは消えました。

この経験をきっかけに、Homebrew の OpenJDK と他のディストリビューションの JDK が何を違いとして持っているのかを整理しました。

ディレクトリ構造の違い

Homebrew OpenJDK — Linux 風 Flat 構造

Homebrew は Linux のパッケージ管理規約に倣った構造でインストールします。

/opt/homebrew/opt/openjdk/
├── bin/
├── include/
├── lib/
│   └── jvm/
└── libexec/
    └── openjdk.jdk/
        └── Contents/        ← 内部に隠蔽
            └── Home/

Apple が期待する macOS バンドルの構造は Contents/ を最上位に持ちますが、Homebrew はこれを libexec/ 以下に隠蔽しています。
/Library/Java/JavaVirtualMachines/ へのシンボリックリンクは張られるものの、jpackage のランタイム検出はリンク先の実体パスを参照するため、整合性が崩れることがあります。

Distribution JDKs(Temurin / Oracle / Microsoft)— Apple Bundle 構造

Cask 経由でインストールされる JDK は /Library/Java/JavaVirtualMachines/ に正規の macOS バンドルとして配置されます。

/Library/Java/JavaVirtualMachines/temurin-21.jdk/
└── Contents/
    ├── Home/          ← JAVA_HOME
    │   ├── bin/
    │   └── lib/
    ├── Info.plist     ← macOS バンドルメタデータ
    └── MacOS/

この構造は /usr/libexec/java_home による JDK 検出、jpackage のランタイムバンドル解決、codesign / notarytool のいずれとも完全に適合しています。

jpackage における具体的な問題

問題 原因 影響範囲
ランタイム検出失敗 Info.plist の欠如または不正なパス --runtime-image 指定時
コード署名エラー .app バンドル構造との不整合 Apple Silicon 含む全環境
Notarization 拒否 Apple ツールチェーンが期待する JDK 構造との乖離 配布パッケージ全般
jlink モジュールエラー symlink 越しのモジュールパス解決の失敗 カスタムランタイム生成時

シンボリックリンクとパス解決

Homebrew は /Library/Java/JavaVirtualMachines/ への symlink を提供していますが、jpackage 内部では realpath() による実体パス解決が行われます。
その結果、--runtime-image--jdk-path に渡したパスが期待する JDK バンドル構造と一致せず、エラーが発生します。

以下のコマンドで実体パスを確認できます。

$ readlink -f $(which java)
# Homebrew: /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java

$ /usr/libexec/java_home -V
# Temurin: /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home

推奨される代替手段

jpackage を使うプロジェクトでは、以下のいずれかを使用してください。

  • Temurin(Eclipse Adoptium) — 最も実績が多く、CI/CD との相性も良好です
  • Microsoft Build of OpenJDK — Azure DevOps との統合が優れています
  • Oracle JDK — 商用サポートが必要な場合に適しています

まとめ

Homebrew の OpenJDK は開発・テスト・CI 実行用途では問題なく機能します。
しかし macOS 配布パッケージの生成(jpackage)、コード署名、Notarization が絡む場面では、Apple バンドル構造に準拠した JDK を使うべきです。

用途に応じて JDK を使い分けることが、macOS ネイティブ開発における現実的なアプローチです。