M1 MacでDockerのMySQLが起動しない問題とその解決法

背景

Apple Silicon (M1) チップを搭載したMac環境下でDockerを使用してMySQLサービスを立ち上げようとすると、起動失敗の事象が報告されています。

詳細なログ分析 (docker compose logs) を行うと、以下のエラーが出力されていることが確認されます。

qemu: uncaught target signal 11 (Segmentation fault) - core dumped

このエラーは、QEMUエミュレータがApple Silicon上でのx86_64アーキテクチャのバイナリエミュレーション中にセグメンテーションフォールトを引き起こしていることを示しています。

これは、ARMアーキテクチャネイティブのプロセスがx86_64の命令セットを直接解釈しようとした際に発生する典型的な問題です。

解決策

この問題を解決するための手順は、Docker Desktopの設定を介してRosetta 2エミュレーションレイヤーを活用することです。

具体的には、以下のステップに従ってください:

  1. Docker Desktopアプリケーションを開き、設定にアクセスします。
    これは、アプリケーションウィンドウの右上隅にある歯車アイコンをクリックすることで行えます。
  2. 「General」タブに進み、Use Rosetta for x86/amd64 emulation on Apple Siliconオプションを探し、このオプションを有効化します。
    これにより、Dockerが内部的にRosetta 2を使用してx86_64アーキテクチャ用のバイナリをARMアーキテクチャ上で透過的にエミュレートすることが可能になります。
  3. 設定を適用後、Docker Desktopを再起動して変更を有効にします。

この手順により、M1チップ搭載Mac上でのMySQLの起動問題を回避し、Dockerコンテナ内でのサービスの安定稼働を確保できるようになります。

MySQLでViewが存在しないときだけ作成するには

問題

Viewが存在しないときだけ、Viewを作成するようなSQLを書きたいときがあります。

しかし、MySQLではCREATE VIEW IF NOT EXISTSの構文は直接サポートされていません。

解決策

CREATE VIEW IF NOT EXISTSの代わりにCREATE OR REPLACE VIEWを使用します。

この方法は、指定された名前のViewがすでに存在する場合はそれを置き換え、存在しない場合は新しく作成します。

CREATE OR REPLACE VIEW test.v AS SELECT * FROM t;

このステートメントは次のように動作します:

  • test.vという名前のViewがすでに存在する場合:
    • そのビューを削除し、新しい定義でViewを再作成します。
  • test.vという名前のViewが存在しない場合:
    • 新しいViewを作成します。

この方法の利点は、Viewが既に存在するかどうかに関わらず、常にViewが最新の定義になることを保証できる点です。

ただし、大規模なデータセットや複雑なクエリを含むビューでは、パフォーマンスに影響を与える可能性があります。
アプリケーション側でビューの存在確認を行い、必要に応じてCREATE VIEWやALTER VIEWを実行するという方法もあります。

ユニットテストでGuzzleを使うときにモックを使う方法

Guzzleは、外部のAPIとの通信によく使われます。しかし、ユニットテストの際には外部のAPIと実際に通信を行いたくない場合が多いです。

このような状況でモックを利用して、Guzzleを使ったコードのテストを行う方法を紹介します。

モックを使ったテストコードの例

以下のPHPコードは、Guzzleを使用してHTTPリクエストをモックする方法を示しています。

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

// 期待されるJSONレスポンスボディを定義
$json = [
    'data' => [
        'id' => 1,
        'name' => 'test',
    ]
];
// モックレスポンスを作成
$mock = new MockHandler([new Response(200, [], json_encode($json))]);
// モックを含むハンドラースタックを作成し、クライアントに渡す
$client = new Client(['handler' => HandlerStack::create($mock)]);

// モックを使ってHTTPリクエストを行う
$response = $client->get('http://example.com');

// ステータスコードを検証
$this->assertEquals(200, $response->getStatusCode());
// レスポンスボディが期待するJSONデータであることを検証
$body = json_decode((string) $response->getBody(), true);
$this->assertEquals($json, $body);

解説

GuzzleHttp\Clientのカスタマイズ

GuzzleHttp\Clientのコンストラクタにoptions引数を渡すことで、カスタムハンドラーを設定できます。

この例では、モック用のハンドラーを設定しています。

// モックを含むハンドラースタックを作成し、クライアントに渡す
$client = new Client(['handler' => HandlerStack::create($mock)]);

モックの作成

GuzzleHttp\Handler\MockHandlerを使ってHTTPレスポンスをモックします。

このクラスのインスタンスを作成し、コンストラクタにGuzzleHttp\Psr7\Responseオブジェクトの配列を渡します。

// モックレスポンスを作成
$mock = new MockHandler([new Response(200, [], json_encode($json))]);

レスポンスの構築

GuzzleHttp\Psr7\Responseクラスのインスタンスは、HTTPレスポンスを模倣します。

コンストラクタには、ステータスコード、ヘッダー、ボディを順番に渡します。

// 期待されるJSONレスポンスボディを定義
$json = [
    'data' => [
        'id' => 1,
        'name' => 'test',
    ]
];
// モックレスポンスを作成
$mock = new MockHandler([new Response(200, [], json_encode($json))]);

これで、Guzzleを使用したユニットテストでモックを利用する方法の解説は終わりです。

モックを活用することで、実際に外部のAPIと通信することなく、コードのテストを行うことができます。

LaravelからSlackにメッセージを送信する

SlackのWebhook URLを取得する

(1) Incoming Webhookのページを開きます。

(2) メッセージを送信するチャンネルを選択して、「Incoming Webhookインテグレーションの追加」ボタンをクリックします。

(3) Webhook URLをコピーします。

(4)「設定を保存する」ボタンをクリックします。

Laravelの設定

セキュリティのため、.envファイルにWebhook URLを設定します。

この値は公開されるべきではないため、注意して管理してください。

# Slack Webhook URL
SLACK_WEBHOOK_URL=<コピーしたWebhook URLを貼り付ける>
# Slackの投稿者の名前
SLACK_USERNAME=webhookbot
# Slackの投稿者のアイコン
SLACK_ICON_EMOJI=:ghost:

Laravelプロジェクトの config ディレクトリ内に slack.php ファイルを作成し、以下の内容を貼り付けます。

<?php
// config/slack.php
return [
    'webhook_url' => env('SLACK_WEBHOOK_URL'),
    'username' => env('SLACK_USERNAME'),
    'icon_emoji' => env('SLACK_ICON_EMOJI'),
];

Slackにメッセージを送信するクラスを作成する

Slackにメッセージを送信するクラスを作成します。

<?php
declare(strict_types=1);

// app/Services/SlackService.php
namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;

class SlackService
{
    public static function post(string $message): bool
    {
        $client = new Client();

        $webhookUrl = config('slack.webhook_url');
        $username = config('slack.username');
        $iconEmoji = config('slack.icon_emoji');

        try {
            $client->request(
                'POST',
                $webhookUrl,
                [
                    'headers' => [
                        'Content-Type' => 'application/json',
                    ],
                    'body' => json_encode([
                        'username' => $username,
                        'text' => $message,
                        'icon_emoji' => $iconEmoji,
                    ]),
                ]);
            return true;
        } catch (GuzzleException $e) {
            Log::error('Slack Post Failed: '.$e->getMessage());
            Log::error('Failed message: '.$message);
            Log::error($e->getTraceAsString());
            return false;
        }
    }
}

Slackにメッセージを送信する

SlackServiceを使用して、メッセージを送信します。

例として、コントローラーから以下のように呼び出すことができます。

use App\Services\SlackService;

// コントローラーのメソッド内で
SlackService::post('Hello, Slack!');