PHP Slim完全ガイド:軽量級フレームワークで作る高速なWebアプリケーション

Slim Frameworkとは:特徴と強み

Slim Frameworkは、PHP開発者の間で高い評価を受けているマイクロフレームワークです。「必要最小限の機能」と「高い拡張性」を兼ね備え、REST APIやシンプルなWebアプリケーションの開発に最適なツールとして知られています。

マイクロフレームワークの王道:軽量で高速な開発が可能

Slim Frameworkの最大の特徴は、その名の通り「スリム」であることです。基本機能に絞られたコアパッケージは、わずか数百KBのサイズに収まっています。この軽量さがもたらす利点は以下の通りです:

  1. 優れたパフォーマンス
  • 最小限のオーバーヘッド
  • リクエスト処理の高速化
  • メモリ使用量の最適化
  1. 短い学習曲線
  • シンプルな設計思想
  • 直感的なルーティングAPI
  • 明確なドキュメント
  1. 柔軟なカスタマイズ性
   // ミドルウェアの追加が簡単
   $app->add(new \Slim\Middleware\Session());

   // PSR-7準拠のリクエスト/レスポンス処理
   $app->get('/api/users', function (Request $request, Response $response) {
       return $response->withJson(['status' => 'success']);
   });
  1. モダンなPHP機能のサポート
  • PSR-7, PSR-11, PSR-15対応
  • Composer対応
  • PHP 7.4以上の新機能活用

Laravel・Symfonyとの違い:Slimを選ぶべきシーン

Slim FrameworkとPHPの主要フレームワークを比較することで、その独自の位置づけが明確になります。

機能比較SlimLaravelSymfony
初期サイズ~300KB~10MB~8MB
学習コスト中〜高
機能の豊富さ最小限豊富非常に豊富
カスタマイズ性
開発速度速い普通普通

Slimが最適なシーン:

  1. マイクロサービスの開発
  • 軽量なAPIエンドポイント
  • 特定の機能に特化したサービス
  • コンテナ化された環境での運用
  1. プロトタイプの迅速な開発
  • MVP(最小実行製品)の作成
  • プルーフオブコンセプト
  • 実験的な機能の実装
  1. リソースが限られた環境での運用
   // 最小限の設定でアプリケーションを起動
   $app = AppFactory::create();
   $app->get('/', function (Request $request, Response $response) {
       $response->getBody()->write('Hello, World!');
       return $response;
   });
   $app->run();
  1. レガシーシステムのモダン化
  • 段階的なリプレース
  • APIゲートウェイとしての活用
  • 既存システムとの統合

選択の際の重要なポイントは、必要な機能が明確になっていることです。フルスタックフレームワークが提供する多くの機能が不要な場合、Slimは理想的な選択肢となります。特に、マイクロサービスアーキテクチャの採用やAPIの開発において、その真価を発揮します。

Slim Framework 導入からHello Worldまで

Composerを使った最新版のインストール方法

Slim Framework 4.xのインストールは、Composerを使用することで簡単に行えます。以下のステップに従って環境を構築していきましょう。

  1. プロジェクトディレクトリの作成
   mkdir my-slim-app
   cd my-slim-app
  1. Composerの初期化
   composer init --no-interaction \
       --name=myapp/slim-project \
       --description="My First Slim Framework Project" \
       --author="Your Name <you@example.com>" \
       --require="slim/slim:^4.12" \
       --require-dev="phpunit/phpunit:^10.0"
  1. 必要なパッケージのインストール
   # PSR-7実装とファクトリーのインストール
   composer require slim/psr7

   # PHP-DIのインストール(依存性注入コンテナ)
   composer require php-di/php-di

プロジェクトの基本構造は以下のようになります:

my-slim-app/
├── composer.json
├── composer.lock
├── public/
│   └── index.php
├── src/
│   └── Controllers/
└── vendor/

基本的なルーティングの設定方法

Slim Frameworkのルーティングは、直感的で柔軟な設計が特徴です。基本的な設定方法を見ていきましょう。

  1. エントリーポイントの作成 (public/index.php)
   <?php
   use Psr\Http\Message\ResponseInterface as Response;
   use Psr\Http\Message\ServerRequestInterface as Request;
   use Slim\Factory\AppFactory;

   require __DIR__ . '/../vendor/autoload.php';

   // アプリケーションの初期化
   $app = AppFactory::create();

   // ミドルウェアの追加
   $app->addErrorMiddleware(true, true, true);

   // ベーシックなルート
   $app->get('/', function (Request $request, Response $response) {
       $response->getBody()->write('Hello World!');
       return $response;
   });

   // アプリケーションの実行
   $app->run();
  1. 様々なHTTPメソッドの利用
   // GETリクエスト
   $app->get('/users', function (Request $request, Response $response) {
       // ユーザー一覧の取得
       return $response->withJson(['users' => ['John', 'Jane']]);
   });

   // POSTリクエスト
   $app->post('/users', function (Request $request, Response $response) {
       // 新規ユーザーの作成
       $data = $request->getParsedBody();
       return $response->withJson(['status' => 'created']);
   });

   // プレースホルダーの使用
   $app->get('/users/{id}', function (Request $request, Response $response, array $args) {
       $userId = $args['id'];
       return $response->withJson(['user_id' => $userId]);
   });

シンプルなAPIエンドポイントの作成

実践的なAPIエンドポイントの作成例を見ていきましょう。以下は、基本的なCRUD操作を実装した例です。

<?php
// src/Controllers/UserController.php

namespace App\Controllers;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class UserController
{
    private $users = [
        ['id' => 1, 'name' => '山田太郎', 'email' => 'yamada@example.com'],
        ['id' => 2, 'name' => '鈴木花子', 'email' => 'suzuki@example.com']
    ];

    public function getUsers(Request $request, Response $response): Response
    {
        $response->getBody()->write(json_encode([
            'status' => 'success',
            'data' => $this->users
        ]));

        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus(200);
    }

    public function getUserById(Request $request, Response $response, array $args): Response
    {
        $userId = (int) $args['id'];
        $user = array_filter($this->users, fn($u) => $u['id'] === $userId);

        if (empty($user)) {
            return $response
                ->withHeader('Content-Type', 'application/json')
                ->withStatus(404);
        }

        $response->getBody()->write(json_encode([
            'status' => 'success',
            'data' => reset($user)
        ]));

        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus(200);
    }
}

これらのエンドポイントをindex.phpで登録します:

// コントローラーのルート登録
$app->get('/api/users', [UserController::class, 'getUsers']);
$app->get('/api/users/{id}', [UserController::class, 'getUserById']);

このAPIは以下のような形式でテストできます:

# ユーザー一覧の取得
curl http://localhost:8080/api/users

# 特定のユーザーの取得
curl http://localhost:8080/api/users/1

開発サーバーの起動は以下のコマンドで行います:

# PHPの内蔵サーバーを使用
php -S localhost:8080 -t public

これで基本的なSlim Frameworkの環境構築とAPIの実装が完了しました。次のステップでは、より実践的なアプリケーション開発手法について見ていきます。

実践的なアプリケーション開発手法

ミドルウェアを活用したリクエスト処理の実装

ミドルウェアは、HTTPリクエストの処理パイプラインを柔軟にカスタマイズできる強力な機能です。認証、ロギング、CORSなど、様々な横断的関心事を効率的に処理できます。

  1. 基本的なミドルウェアの実装
<?php
// src/Middleware/JsonResponseMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class JsonResponseMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);
        return $response->withHeader('Content-Type', 'application/json');
    }
}
  1. 認証ミドルウェアの例
<?php
// src/Middleware/AuthMiddleware.php
namespace App\Middleware;

class AuthMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandler $handler): Response
    {
        $token = $request->getHeaderLine('Authorization');

        if (empty($token)) {
            $response = new Response();
            return $response
                ->withStatus(401)
                ->withJson(['error' => '認証が必要です']);
        }

        // トークンの検証ロジック
        if (!$this->validateToken($token)) {
            $response = new Response();
            return $response
                ->withStatus(403)
                ->withJson(['error' => '無効なトークンです']);
        }

        return $handler->handle($request);
    }

    private function validateToken(string $token): bool
    {
        // トークン検証の実装
        return true; // 実際の検証ロジックを実装
    }
}
  1. ミドルウェアの登録
// ミドルウェアのグローバル適用
$app->add(new JsonResponseMiddleware());

// 特定のルートグループにのみ適用
$app->group('/api', function (Group $group) {
    $group->get('/users', [UserController::class, 'getUsers']);
})->add(new AuthMiddleware());

データベース連携:PDOとEloquentの活用

Slim FrameworkではPDOを直接使用するか、EloquentなどのORMを統合することで、データベース操作を効率的に行えます。

  1. PDOを使用した実装
<?php
// src/Database/Database.php
namespace App\Database;

class Database
{
    private static $instance = null;
    private $pdo;

    private function __construct()
    {
        $host = $_ENV['DB_HOST'] ?? 'localhost';
        $dbname = $_ENV['DB_NAME'] ?? 'slim_app';
        $user = $_ENV['DB_USER'] ?? 'root';
        $pass = $_ENV['DB_PASS'] ?? '';

        $dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
        $this->pdo = new \PDO($dsn, $user, $pass, [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
            \PDO::ATTR_EMULATE_PREPARES => false,
        ]);
    }

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection(): \PDO
    {
        return $this->pdo;
    }
}

// 使用例
class UserRepository
{
    private $db;

    public function __construct()
    {
        $this->db = Database::getInstance()->getConnection();
    }

    public function findById(int $id): ?array
    {
        $stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        return $stmt->fetch() ?: null;
    }
}
  1. Eloquentの統合
<?php
// config/database.php
use Illuminate\Database\Capsule\Manager as Capsule;

$capsule = new Capsule;
$capsule->addConnection([
    'driver'    => 'mysql',
    'host'      => $_ENV['DB_HOST'],
    'database'  => $_ENV['DB_NAME'],
    'username'  => $_ENV['DB_USER'],
    'password'  => $_ENV['DB_PASS'],
    'charset'   => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix'    => '',
]);

$capsule->setAsGlobal();
$capsule->bootEloquent();

// src/Models/User.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = ['name', 'email'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

DIコンテナを使った依存性の管理

依存性注入(DI)コンテナを使用することで、クラスの依存関係を効率的に管理し、テスタビリティを向上させることができます。

<?php
// config/container.php
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use App\Services\UserService;
use App\Repositories\UserRepository;

$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions([
    UserService::class => function (ContainerInterface $c) {
        return new UserService($c->get(UserRepository::class));
    },

    UserRepository::class => function (ContainerInterface $c) {
        return new UserRepository($c->get(\PDO::class));
    },

    \PDO::class => function (ContainerInterface $c) {
        return Database::getInstance()->getConnection();
    }
]);

$container = $containerBuilder->build();
AppFactory::setContainer($container);

// サービスクラスの例
class UserService
{
    private $repository;

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    public function createUser(array $data): User
    {
        // バリデーション
        $this->validateUserData($data);

        // ユーザー作成
        return $this->repository->create($data);
    }

    private function validateUserData(array $data): void
    {
        if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('有効なメールアドレスが必要です');
        }

        if (empty($data['name']) || strlen($data['name']) < 2) {
            throw new \InvalidArgumentException('名前は2文字以上必要です');
        }
    }
}

これらの実装パターンを組み合わせることで、保守性が高く、スケーラブルなアプリケーションを構築できます。次のセクションでは、これらの実装をベースにしたパフォーマンスチューニングについて説明していきます。

パフォーマンスチューニングのベストプラクティス

キャッシュ戦略:アプリケーションの高速化方法

Slim Frameworkでは、様々なレベルでキャッシュを実装できます。適切なキャッシュ戦略を採用することで、アプリケーションのレスポンス時間を大幅に改善できます。

  1. ルートキャッシュの実装
<?php
// src/Cache/RouteCache.php
namespace App\Cache;

use Psr\SimpleCache\CacheInterface;
use Slim\Interfaces\RouteCollectorProxyInterface;

class RouteCache
{
    private $cache;
    private $ttl;

    public function __construct(CacheInterface $cache, int $ttl = 3600)
    {
        $this->cache = $cache;
        $this->ttl = $ttl;
    }

    public function cacheRoute($route, callable $handler)
    {
        return function ($request, $response, $args) use ($handler, $route) {
            $cacheKey = "route_" . md5($route . json_encode($args));

            // キャッシュの確認
            if ($cachedResponse = $this->cache->get($cacheKey)) {
                return $response->withJson($cachedResponse);
            }

            // 実際の処理を実行
            $result = $handler($request, $response, $args);

            // 結果をキャッシュ
            $this->cache->set($cacheKey, $result, $this->ttl);

            return $response->withJson($result);
        };
    }
}
  1. Redisを使用したキャッシュシステム
<?php
// config/cache.php
use Redis;

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// キャッシュクラスの実装
class RedisCache implements CacheInterface
{
    private $redis;

    public function __construct(Redis $redis)
    {
        $this->redis = $redis;
    }

    public function get($key, $default = null)
    {
        $result = $this->redis->get($key);
        return $result !== false ? json_decode($result, true) : $default;
    }

    public function set($key, $value, $ttl = null)
    {
        $this->redis->set($key, json_encode($value));
        if ($ttl) {
            $this->redis->expire($key, $ttl);
        }
    }
}

メモリ使用量の最適化テクニック

メモリの効率的な使用は、アプリケーションのスケーラビリティに直接影響します。以下の最適化テクニックを実装することで、メモリ使用量を削減できます。

  1. ジェネレータを使用した大量データの処理
<?php
class UserExporter
{
    private $pdo;

    public function exportUsers(): \Generator
    {
        $stmt = $this->pdo->prepare('SELECT * FROM users');
        $stmt->execute();

        while ($row = $stmt->fetch()) {
            yield $row;
        }
    }
}

// APIエンドポイントでの使用例
$app->get('/api/users/export', function (Request $request, Response $response) {
    $exporter = new UserExporter($this->get('pdo'));

    $response = $response->withHeader('Content-Type', 'application/json');
    $stream = $response->getBody();

    $stream->write('[');
    $first = true;

    foreach ($exporter->exportUsers() as $user) {
        if (!$first) {
            $stream->write(',');
        }
        $stream->write(json_encode($user));
        $first = false;
    }

    $stream->write(']');
    return $response;
});
  1. メモリリーク防止のベストプラクティス
<?php
class ResourceManager
{
    private $resources = [];

    public function __destruct()
    {
        // リソースの明示的な解放
        foreach ($this->resources as $resource) {
            if ($resource instanceof \PDOStatement) {
                $resource->closeCursor();
            }
        }
        $this->resources = [];
    }

    public function addResource($resource)
    {
        $this->resources[] = $resource;
    }
}

// 大きな配列の処理
function processLargeArray(array $data)
{
    $chunk_size = 1000;
    $chunks = array_chunk($data, $chunk_size);

    foreach ($chunks as $chunk) {
        // チャンク単位で処理
        processChunk($chunk);
        // メモリの解放
        unset($chunk);
    }
}

本番環境での実践的なデプロイ方法

本番環境でのデプロイには、パフォーマンスとセキュリティの両面で注意が必要です。以下のベストプラクティスを実装することをお勧めします。

  1. 本番環境設定の最適化
<?php
// config/production.php
return [
    'displayErrorDetails' => false,
    'logErrors' => true,
    'logErrorDetails' => true,
    'logger' => [
        'name' => 'slim-app',
        'path' => __DIR__ . '/../logs/app.log',
        'level' => \Monolog\Logger::WARNING,
    ],
    'db' => [
        'connection_limit' => 100,
        'timeout' => 5,
        'persistent' => true,
    ],
    'cache' => [
        'enabled' => true,
        'ttl' => 3600,
    ],
];

// アプリケーション設定の適用
$settings = require __DIR__ . '/config/production.php';
$app = AppFactory::create();
$app->addRoutingMiddleware();
$errorMiddleware = $app->addErrorMiddleware(
    $settings['displayErrorDetails'],
    $settings['logErrors'],
    $settings['logErrorDetails']
);
  1. OPcacheの最適化設定
; php.ini の設定例
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
  1. デプロイスクリプトの例
#!/bin/bash
# deploy.sh

# アプリケーションディレクトリ
APP_DIR="/var/www/slim-app"

# 依存関係のインストール
composer install --no-dev --optimize-autoloader

# キャッシュのクリア
php artisan cache:clear
php artisan config:cache
php artisan route:cache

# OPcacheのリセット
curl -X GET http://localhost/opcache-reset.php

# パーミッションの設定
chown -R www-data:www-data $APP_DIR/storage
chmod -R 755 $APP_DIR/storage

# Nginxの設定例
server {
    listen 80;
    server_name example.com;
    root /var/www/slim-app/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }
}

これらの最適化テクニックを組み合わせることで、高パフォーマンスで安定したアプリケーションを実現できます。次のセクションでは、セキュリティ対策と運用のポイントについて説明していきます。

セキュリティ対策と運用のポイント

一般的な脆弱性対策とスリム固有の注意点

Slim Frameworkでは、基本的なセキュリティ対策に加えて、フレームワーク特有の注意点があります。以下の実装例を参考に、堅牢なセキュリティ対策を実現しましょう。

  1. XSS対策の実装
<?php
// src/Middleware/SecurityHeadersMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class SecurityHeadersMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);

        return $response
            ->withHeader('X-Frame-Options', 'DENY')
            ->withHeader('X-Content-Type-Options', 'nosniff')
            ->withHeader('X-XSS-Protection', '1; mode=block')
            ->withHeader('Content-Security-Policy', "default-src 'self'");
    }
}

// HTMLエスケープユーティリティ
class HtmlPurifier
{
    public static function clean(string $input): string
    {
        return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
    }

    public static function cleanArray(array $input): array
    {
        return array_map([self::class, 'clean'], $input);
    }
}
  1. CSRF対策の実装
<?php
// src/Middleware/CsrfMiddleware.php
namespace App\Middleware;

class CsrfMiddleware implements MiddlewareInterface
{
    private $session;

    public function __construct(SessionInterface $session)
    {
        $this->session = $session;
    }

    public function process(Request $request, RequestHandler $handler): Response
    {
        // GETリクエストはスキップ
        if ($request->getMethod() === 'GET') {
            return $handler->handle($request);
        }

        $token = $request->getHeaderLine('X-CSRF-Token');
        if (!$this->validateToken($token)) {
            throw new HttpForbiddenException($request, 'Invalid CSRF token');
        }

        return $handler->handle($request);
    }

    private function validateToken(string $token): bool
    {
        $storedToken = $this->session->get('csrf_token');
        return hash_equals($storedToken, $token);
    }

    public function generateToken(): string
    {
        $token = bin2hex(random_bytes(32));
        $this->session->set('csrf_token', $token);
        return $token;
    }
}

// テンプレートでのトークン出力
$app->get('/form', function (Request $request, Response $response) {
    $csrf = $this->get(CsrfMiddleware::class);
    $token = $csrf->generateToken();

    return $this->get('view')->render($response, 'form.php', [
        'csrf_token' => $token
    ]);
});

ログ管理とエラーハンドリングの実装

効果的なログ管理とエラーハンドリングは、問題の早期発見と解決に不可欠です。

  1. 構造化ログの実装
<?php
// src/Logger/StructuredLogger.php
namespace App\Logger;

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;

class StructuredLogger
{
    private $logger;

    public function __construct(string $name)
    {
        $this->logger = new Logger($name);

        $handler = new StreamHandler(
            __DIR__ . '/../../logs/app.log',
            Logger::INFO
        );
        $handler->setFormatter(new JsonFormatter());

        $this->logger->pushHandler($handler);
    }

    public function logRequest(Request $request, array $context = []): void
    {
        $this->logger->info('HTTP Request', [
            'method' => $request->getMethod(),
            'uri' => (string) $request->getUri(),
            'headers' => $request->getHeaders(),
            'ip' => $request->getServerParam('REMOTE_ADDR'),
            'user_agent' => $request->getHeaderLine('User-Agent'),
            ...$context
        ]);
    }

    public function logError(\Throwable $error, array $context = []): void
    {
        $this->logger->error($error->getMessage(), [
            'file' => $error->getFile(),
            'line' => $error->getLine(),
            'trace' => $error->getTraceAsString(),
            ...$context
        ]);
    }
}
  1. カスタムエラーハンドラの実装
<?php
// src/Error/CustomErrorHandler.php
namespace App\Error;

use Psr\Http\Message\ResponseInterface;
use Slim\Exception\HttpNotFoundException;
use Slim\Interfaces\ErrorHandlerInterface;

class CustomErrorHandler implements ErrorHandlerInterface
{
    private $logger;
    private $displayErrors;

    public function __construct(StructuredLogger $logger, bool $displayErrors = false)
    {
        $this->logger = $logger;
        $this->displayErrors = $displayErrors;
    }

    public function __invoke(
        Request $request,
        Throwable $exception,
        bool $displayErrorDetails,
        bool $logErrors,
        bool $logErrorDetails
    ): ResponseInterface
    {
        // エラーのログ記録
        $this->logger->logError($exception);

        // エラーレスポンスの生成
        $payload = [
            'error' => [
                'message' => $this->displayErrors 
                    ? $exception->getMessage() 
                    : 'An error occurred',
                'code' => 500
            ]
        ];

        if ($exception instanceof HttpNotFoundException) {
            $payload['error']['code'] = 404;
        }

        $response = new Response();
        $response->getBody()->write(json_encode($payload));

        return $response
            ->withStatus($payload['error']['code'])
            ->withHeader('Content-Type', 'application/json');
    }
}

ユニットテストとCIの導入方法

自動テストとCI/CDパイプラインの導入により、品質を担保しながら継続的なデプロイを実現できます。

  1. ユニットテストの実装例
<?php
// tests/Controllers/UserControllerTest.php
namespace Tests\Controllers;

use PHPUnit\Framework\TestCase;
use App\Controllers\UserController;

class UserControllerTest extends TestCase
{
    private $controller;
    private $mockRepository;

    protected function setUp(): void
    {
        $this->mockRepository = $this->createMock(UserRepository::class);
        $this->controller = new UserController($this->mockRepository);
    }

    public function testGetUsers()
    {
        $expectedUsers = [
            ['id' => 1, 'name' => 'Test User']
        ];

        $this->mockRepository
            ->expects($this->once())
            ->method('findAll')
            ->willReturn($expectedUsers);

        $request = $this->createMock(Request::class);
        $response = $this->createMock(Response::class);

        $result = $this->controller->getUsers($request, $response);

        $this->assertInstanceOf(Response::class, $result);
        // レスポンスの内容を検証
    }
}
  1. GitHub Actionsを使用したCI設定
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.1'
        extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, dom, filter, gd, json, xmlwriter
        coverage: xdebug

    - name: Validate composer.json and composer.lock
      run: composer validate

    - name: Install dependencies
      run: composer install --prefer-dist --no-progress --no-suggest

    - name: Run test suite
      run: composer run-script test

    - name: Run PHP CS Fixer
      run: composer run-script cs-fix

    - name: Run PHPStan
      run: composer run-script analyse

これらの実装により、セキュアで保守性の高いアプリケーションを継続的に運用できます。特に本番環境では、定期的なセキュリティアップデートとログの監視を忘れずに行いましょう。