【保存版】CodeIgniter 4完全入門ガイド – 環境構築から実践的な開発手法まで解説

CodeIgniter 4 とは?最新バージョンの特徴と強み

軽量で高速なPHPフレームワークの代表格

CodeIgniter 4(以下、CI4)は、シンプルながらパワフルな機能を備えたPHPフレームワークとして知られています。特に以下の特徴が、多くの開発者から支持されている理由です:

  1. メモリ効率の高さ
    • フレームワークコアが約2MB未満と軽量
    • 最小限の依存パッケージで動作
    • 効率的なメモリ管理システム
  2. 処理速度の優位性
// パフォーマンス計測例
use CodeIgniter\Debug\Timer;

$timer = new Timer();
$timer->start('benchmark');

// アプリケーションコード
$result = $model->find();

$timer->stop('benchmark');
echo $timer->getElapsedTime('benchmark'); // 一般的なフレームワークの1.5-2倍の速度
  1. シンプルなアーキテクチャ
    • 直感的なMVCパターン
    • 明確なディレクトリ構造
    • 最小限の設定で開始可能

バージョン4で劇的に進化した新機能と改善点

  1. モダンPHPの完全採用
namespace App\Controllers;

use CodeIgniter\Controller;
use App\Models\UserModel;

class Users extends Controller
{
    protected $userModel;

    public function __construct()
    {
        $this->userModel = new UserModel();
    }

    public function index()
    {
        return view('users/index', [
            'users' => $this->userModel->findAll()
        ]);
    }
}
  1. 主要な機能強化
    • PSR-4準拠の名前空間対応
    • Composerによる依存管理
    • 強化されたデータベース抽象化層
    • 新しいセキュリティ機能の導入
  2. 開発効率を向上させる新機能
    • 充実したCLIツール
    • エンティティクラスのサポート
    • 高度なルーティングシステム
    • キャッシュシステムの刷新

他のPHPフレームワークと比較したメリット

  1. 開発生産性の比較
機能CodeIgniter 4LaravelSymfony
初期学習時間1-2週間1-2ヶ月2-3ヶ月
設定の複雑さ
ドキュメント充実度
日本語資料
  1. パフォーマンス面での優位性
    • 最小構成時のメモリ使用量:約4MB
    • リクエスト処理時間:平均30%削減
    • アプリケーション起動時間:Laravel比約50%減
  2. プロジェクトに最適な選択基準
    • スピーディーな開発が求められるプロジェクト
    • リソースに制約のある環境での開発
    • APIサーバーの構築
    • 中小規模のWebアプリケーション
  3. フレームワークの柔軟性
    • 最小限の制約
    • 容易なカスタマイズ
    • サードパーティライブラリとの統合のしやすさ

以上の特徴から、CI4は特に「高速な開発」と「高いパフォーマンス」が求められるプロジェクトにおいて、最適な選択肢の一つとなっています。

CodeIgniter 4 の環境構築手順

必要な開発環境とシステム要件

CodeIgniter 4を快適に動作させるために、以下のシステム要件を満たす必要があります:

  1. PHP要件
    • PHP バージョン 7.4以上(8.0以上推奨)
    • 必須PHP拡張機能:
      • intl
      • json
      • mbstring
      • mysqlnd(MySQLを使用する場合)
      • xml
      • curl
  2. データベース要件(選択可能)
    • MySQL (5.1+)
    • PostgreSQL (7.4+)
    • SQLite3
    • Microsoft SQL Server (2005+)
  3. Webサーバー要件
    • Apache 2.4+ with mod_rewrite
    • Nginx 1.13+
    # Nginxの基本設定例 location / { try_files $uri $uri/ /index.php$is_args$args; }
  4. 推奨開発ツール
    • Composer(パッケージ管理)
    • Git(バージョン管理)
    • VS Code / PHPStorm(IDE)

Composer を使用したインストール方法

  1. プロジェクトの新規作成
# プロジェクト作成コマンド
composer create-project codeigniter4/appstarter ci4-project

# プロジェクトディレクトリへ移動
cd ci4-project

# 開発サーバーの起動
php spark serve
  1. 既存プロジェクトへの導入
# Composerを使用してCodeIgniter 4をインストール
composer require codeigniter4/framework

# 設定ファイルとpublicディレクトリの準備
php spark install:config
  1. インストール後の確認
# バージョン確認
php spark --version

# 環境チェック
php spark env:check

# ルートの確認
php spark routes

初期設定と基本的な環境設定のポイント

  1. 環境設定ファイルの準備
# .envファイルの作成
cp env .env

# 環境設定の編集
# CI_ENVIRONMENT = development
# app.baseURL = 'http://localhost:8080/'
  1. データベース設定
// app/Config/Database.php
public $default = [
    'DSN'      => '',
    'hostname' => 'localhost',
    'username' => 'your_username',
    'password' => 'your_password',
    'database' => 'your_database',
    'DBDriver' => 'MySQLi',
    'DBPrefix' => '',
    'pConnect' => false,
    'DBDebug'  => true,
    'charset'  => 'utf8',
    'DBCollat' => 'utf8_general_ci',
    'swapPre'  => '',
    'encrypt'  => false,
    'compress' => false,
    'strictOn' => false,
    'failover' => [],
    'port'     => 3306,
];
  1. セキュリティ設定
// app/Config/Security.php
public $tokenName  = 'csrf_token_name';
public $headerName = 'X-CSRF-TOKEN';
public $cookieName = 'csrf_cookie_name';
public $expires    = 7200;
  1. アプリケーション基本設定
// app/Config/App.php
public $baseURL = 'http://localhost:8080/';
public $indexPage = '';
public $defaultLocale = 'ja';
public $negotiateLocale = true;
public $supportedLocales = ['en', 'ja'];
  1. エラー報告とロギング設定
// app/Config/Logger.php
public $threshold = 4; // 開発環境では4(全てのログを記録)

// ログハンドラの設定
public $handlers = [
    'file' => [
        'class'     => 'CodeIgniter\Log\Handlers\FileHandler',
        'baseDir'   => WRITEPATH . 'logs/',
        'filePermission' => 0644,
        'fileExtension' => 'log'
    ]
];
  1. 開発環境特有の設定
// 開発時の推奨設定
error_reporting(-1);
ini_set('display_errors', '1');

以上の設定が完了すれば、CodeIgniter 4での開発を開始する準備が整います。環境構築時に特に注意が必要な点は以下の通りです:

  • 本番環境では必ず CI_ENVIRONMENTproduction に設定
  • セキュリティ関連の設定は本番環境で再確認
  • データベース接続情報は環境ごとに適切に管理
  • ログレベルは環境に応じて適切に設定

これらの設定を適切に行うことで、安全で効率的な開発環境を構築することができます。

CodeIgniter 4 の基本アーキテクチャ理解

MVCパターンの実装方法と特徴

CodeIgniter 4のMVCパターンは、シンプルながら強力な実装を提供しています。

  1. コントローラーの基本実装
namespace App\Controllers;

use CodeIgniter\Controller;
use App\Models\UserModel;

class Users extends Controller
{
    protected $userModel;

    public function __construct()
    {
        // モデルのインスタンス化
        $this->userModel = new UserModel();
    }

    public function index()
    {
        $data = [
            'users' => $this->userModel->findAll(),
            'title' => 'ユーザー一覧'
        ];

        // ビューの読み込みと表示
        return view('users/index', $data);
    }

    public function show($id)
    {
        $user = $this->userModel->find($id);
        
        if ($user === null) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        return view('users/show', ['user' => $user]);
    }
}
  1. モデルの実装例
namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table      = 'users';
    protected $primaryKey = 'id';
    protected $returnType = 'array';
    protected $allowedFields = ['name', 'email', 'password'];

    // バリデーションルール
    protected $validationRules = [
        'name'     => 'required|min_length[3]',
        'email'    => 'required|valid_email|is_unique[users.email]',
        'password' => 'required|min_length[8]'
    ];

    // カスタムコールバック
    protected $beforeInsert = ['hashPassword'];
    
    protected function hashPassword(array $data)
    {
        if (isset($data['data']['password'])) {
            $data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
        }
        return $data;
    }
}
  1. ビューの実装方法
<!-- app/Views/users/index.php -->
<?= $this->extend('layouts/main') ?>

<?= $this->section('content') ?>
    <h1><?= esc($title) ?></h1>
    
    <div class="user-list">
        <?php foreach ($users as $user): ?>
            <div class="user-item">
                <h3><?= esc($user['name']) ?></h3>
                <p><?= esc($user['email']) ?></p>
                <a href="/users/<?= $user['id'] ?>">詳細を見る</a>
            </div>
        <?php endforeach; ?>
    </div>
<?= $this->endSection() ?>

ルーティングの設定と活用テクニック

  1. 基本的なルーティング設定
// app/Config/Routes.php
$routes->get('/', 'Home::index');
$routes->get('users', 'Users::index');
$routes->get('users/(:num)', 'Users::show/$1');

// RESTfulリソースルート
$routes->resource('api/users');

// グループ化したルート
$routes->group('admin', ['filter' => 'auth'], function($routes) {
    $routes->get('users', 'Admin\Users::index');
    $routes->get('users/new', 'Admin\Users::new');
    $routes->post('users', 'Admin\Users::create');
});
  1. カスタムルートフィルターの実装
// app/Filters/AuthFilter.php
namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class AuthFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        if (!session()->get('logged_in')) {
            return redirect()->to('/login');
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // アクション後の処理
    }
}

依存性注入とサービスの活用方法

  1. サービスクラスの作成
namespace App\Services;

class UserService
{
    protected $userModel;
    protected $emailService;

    public function __construct(UserModel $userModel, EmailService $emailService)
    {
        $this->userModel = $userModel;
        $this->emailService = $emailService;
    }

    public function registerUser(array $userData)
    {
        // トランザクション処理
        $db = \Config\Database::connect();
        $db->transStart();

        try {
            // ユーザー登録
            $userId = $this->userModel->insert($userData);
            
            // ウェルカムメール送信
            $this->emailService->sendWelcomeEmail($userData['email']);
            
            $db->transComplete();
            return $userId;
        } catch (\Exception $e) {
            $db->transRollback();
            throw $e;
        }
    }
}
  1. Services.phpでのサービス登録
// app/Config/Services.php
public static function userService($getShared = true)
{
    if ($getShared) {
        return static::getSharedInstance('userService');
    }

    return new \App\Services\UserService(
        model('UserModel'),
        service('email')
    );
}
  1. コントローラーでのサービス利用
class RegistrationController extends Controller
{
    protected $userService;

    public function __construct()
    {
        $this->userService = service('userService');
    }

    public function register()
    {
        try {
            $userData = $this->request->getPost();
            $userId = $this->userService->registerUser($userData);
            
            return redirect()->to('/login')
                            ->with('success', '登録が完了しました');
        } catch (\Exception $e) {
            return redirect()->back()
                            ->with('error', '登録に失敗しました')
                            ->withInput();
        }
    }
}

これらのアーキテクチャパターンを適切に活用することで、保守性が高く、拡張性のあるアプリケーションを構築することが可能です。特に、依存性注入を活用することで、テスタビリティの向上やコンポーネント間の結合度の低減を実現できます。

データベースオペレーションとモデルの実践的な使い方

モデルクラスを使用したCRUDオペレーションの実装

  1. モデルの基本設定と実装
namespace App\Models;

use CodeIgniter\Model;

class ProductModel extends Model
{
    protected $table = 'products';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    
    protected $allowedFields = [
        'name', 'description', 'price', 
        'category_id', 'stock', 'status'
    ];
    
    // 日付関連のフィールド
    protected $useTimestamps = true;
    protected $dateFormat = 'datetime';
    protected $createdField = 'created_at';
    protected $updatedField = 'updated_at';
    protected $deletedField = 'deleted_at';
    
    // バリデーションルール
    protected $validationRules = [
        'name' => 'required|min_length[3]|max_length[255]',
        'price' => 'required|numeric|greater_than[0]',
        'category_id' => 'required|integer|is_not_unique[categories.id]',
        'stock' => 'required|integer|greater_than_equal_to[0]'
    ];
    
    protected $validationMessages = [
        'name' => [
            'required' => '商品名は必須です',
            'min_length' => '商品名は3文字以上で入力してください'
        ],
        'price' => [
            'required' => '価格は必須です',
            'numeric' => '価格は数値で入力してください',
            'greater_than' => '価格は0より大きい値を入力してください'
        ]
    ];
}
  1. CRUD操作の実装例
class ProductController extends BaseController
{
    protected $productModel;
    
    public function __construct()
    {
        $this->productModel = new ProductModel();
    }
    
    // Create操作
    public function create()
    {
        $data = [
            'name' => $this->request->getPost('name'),
            'price' => $this->request->getPost('price'),
            'category_id' => $this->request->getPost('category_id'),
            'stock' => $this->request->getPost('stock')
        ];
        
        try {
            $this->productModel->insert($data);
            return redirect()->to('/products')
                           ->with('success', '商品が登録されました');
        } catch (\Exception $e) {
            return redirect()->back()
                           ->with('error', '商品の登録に失敗しました')
                           ->withInput();
        }
    }
    
    // Read操作(一覧取得)
    public function index()
    {
        $data['products'] = $this->productModel
            ->select('products.*, categories.name as category_name')
            ->join('categories', 'categories.id = products.category_id')
            ->paginate(10);
            
        $data['pager'] = $this->productModel->pager;
        
        return view('products/index', $data);
    }
    
    // Update操作
    public function update($id)
    {
        $data = $this->request->getPost();
        
        try {
            $this->productModel->update($id, $data);
            return redirect()->to('/products')
                           ->with('success', '商品が更新されました');
        } catch (\Exception $e) {
            return redirect()->back()
                           ->with('error', '商品の更新に失敗しました');
        }
    }
    
    // Delete操作(ソフトデリート)
    public function delete($id)
    {
        try {
            $this->productModel->delete($id);
            return redirect()->to('/products')
                           ->with('success', '商品が削除されました');
        } catch (\Exception $e) {
            return redirect()->back()
                           ->with('error', '商品の削除に失敗しました');
        }
    }
}

クエリビルダの効果的な活用方法

  1. 高度な検索条件の実装
public function search()
{
    $builder = $this->productModel->builder();
    
    // 検索条件の構築
    if ($category = $this->request->getGet('category')) {
        $builder->where('category_id', $category);
    }
    
    if ($minPrice = $this->request->getGet('min_price')) {
        $builder->where('price >=', $minPrice);
    }
    
    if ($maxPrice = $this->request->getGet('max_price')) {
        $builder->where('price <=', $maxPrice);
    }
    
    if ($keyword = $this->request->getGet('keyword')) {
        $builder->groupStart()
                ->like('name', $keyword)
                ->orLike('description', $keyword)
                ->groupEnd();
    }
    
    // 在庫状況でのフィルタリング
    if ($this->request->getGet('in_stock') === 'true') {
        $builder->where('stock >', 0);
    }
    
    return $builder->select('products.*, categories.name as category_name')
                   ->join('categories', 'categories.id = products.category_id')
                   ->orderBy('products.created_at', 'DESC')
                   ->paginate(10);
}
  1. 集計クエリの実装
public function getProductStats()
{
    $builder = $this->productModel->builder();
    
    return $builder->select('
        COUNT(*) as total_products,
        SUM(stock) as total_stock,
        AVG(price) as average_price,
        MIN(price) as min_price,
        MAX(price) as max_price
    ')->get()->getRow();
}

マイグレーションとシーディングの運用手法

  1. マイグレーションファイルの作成と実装
namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateProductsTable extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 11,
                'unsigned' => true,
                'auto_increment' => true
            ],
            'name' => [
                'type' => 'VARCHAR',
                'constraint' => 255
            ],
            'description' => [
                'type' => 'TEXT',
                'null' => true
            ],
            'price' => [
                'type' => 'DECIMAL',
                'constraint' => '10,2'
            ],
            'category_id' => [
                'type' => 'INT',
                'constraint' => 11,
                'unsigned' => true
            ],
            'stock' => [
                'type' => 'INT',
                'constraint' => 11,
                'default' => 0
            ],
            'status' => [
                'type' => 'ENUM',
                'constraint' => ['active', 'inactive'],
                'default' => 'active'
            ],
            'created_at' => [
                'type' => 'DATETIME',
                'null' => true
            ],
            'updated_at' => [
                'type' => 'DATETIME',
                'null' => true
            ],
            'deleted_at' => [
                'type' => 'DATETIME',
                'null' => true
            ]
        ]);
        
        $this->forge->addKey('id', true);
        $this->forge->addForeignKey('category_id', 'categories', 'id', 'CASCADE', 'CASCADE');
        $this->forge->createTable('products');
    }

    public function down()
    {
        $this->forge->dropTable('products');
    }
}
  1. シーダーの実装
namespace App\Database\Seeds;

use CodeIgniter\Database\Seeder;

class ProductSeeder extends Seeder
{
    public function run()
    {
        $data = [
            [
                'name' => 'サンプル商品1',
                'description' => '商品の説明文です。',
                'price' => 1000,
                'category_id' => 1,
                'stock' => 100,
                'status' => 'active',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ],
            // 他のサンプルデータ...
        ];
        
        $this->db->table('products')->insertBatch($data);
    }
}
  1. マイグレーションとシーディングの実行コマンド
# マイグレーションの実行
php spark migrate

# 特定のマイグレーションの実行
php spark migrate --group=products

# マイグレーションのロールバック
php spark migrate:rollback

# シーディングの実行
php spark db:seed ProductSeeder

これらの実装パターンを活用することで、効率的なデータベース操作と保守性の高いアプリケーション開発が可能になります。特に、マイグレーションとシーディングを活用することで、開発環境とテスト環境の一貫性を保ちやすくなります。

セキュリティ対策と実務での注意点

CSRFプロテクションの実装方法

  1. 基本的なCSRF対策の設定
// app/Config/Security.php
public $csrfProtection = 'cookie';
public $tokenName   = 'csrf_token_name';
public $cookieName  = 'csrf_cookie_name';
public $expires     = 7200;
  1. フォームでのCSRFトークンの実装
<!-- ビューファイルでの実装 -->
<form method="post" action="/users/create">
    <?= csrf_field() ?>
    <input type="text" name="username">
    <input type="password" name="password">
    <button type="submit">登録</button>
</form>
  1. APIでのCSRF対策
// app/Filters/APICSRFFilter.php
namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

class APICSRFFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        // APIトークンの検証
        $token = $request->getHeaderLine('X-CSRF-TOKEN');
        if (!$token || !csrf_verify($token)) {
            return service('response')
                ->setStatusCode(403)
                ->setJSON(['error' => 'Invalid CSRF token']);
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
    }
}

XSS対策とバリデーションの設定

  1. 入力バリデーションの実装
// app/Models/UserModel.php
protected $validationRules = [
    'username' => [
        'rules'  => 'required|min_length[4]|max_length[20]|alpha_numeric',
        'errors' => [
            'required' => 'ユーザー名は必須です',
            'min_length' => 'ユーザー名は4文字以上必要です',
            'alpha_numeric' => '英数字のみ使用可能です'
        ]
    ],
    'email' => [
        'rules'  => 'required|valid_email|is_unique[users.email]',
        'errors' => [
            'is_unique' => 'このメールアドレスは既に登録されています'
        ]
    ],
    'password' => [
        'rules'  => 'required|min_length[8]|containsSpecialCharacter[1]|containsNumber[1]',
        'errors' => [
            'containsSpecialCharacter' => '特殊文字を1文字以上含める必要があります',
            'containsNumber' => '数字を1文字以上含める必要があります'
        ]
    ]
];

// カスタムバリデーションルール
public function containsSpecialCharacter(string $str, string $fields, array $data): bool
{
    return preg_match('/[^a-zA-Z0-9]/', $str) > 0;
}
  1. XSS対策の実装
// コントローラーでの出力エスケープ
public function show($id)
{
    $user = $this->userModel->find($id);
    
    return view('users/show', [
        'user' => [
            'name' => esc($user['name']),
            'bio' => esc($user['bio'], 'html'),
            'website' => esc($user['website'], 'url')
        ]
    ]);
}

// ビューでの安全な出力
<div class="user-profile">
    <h1><?= esc($user['name']) ?></h1>
    <div class="bio"><?= esc($user['bio'], 'html') ?></div>
    <a href="<?= esc($user['website'], 'url') ?>">ウェブサイト</a>
</div>

セッション管理とユーザー認証の実装

  1. セッション設定
// app/Config/Session.php
public $driver = 'CodeIgniter\Session\Handlers\FileHandler';
public $cookieName = 'ci_session';
public $expiration = 7200;
public $savePath = WRITEPATH . 'session';
public $matchIP = false;
public $timeToUpdate = 300;
  1. 認証システムの実装
// app/Libraries/Auth.php
namespace App\Libraries;

class Auth
{
    protected $session;
    protected $userModel;

    public function __construct()
    {
        $this->session = service('session');
        $this->userModel = new \App\Models\UserModel();
    }

    public function attempt(string $email, string $password): bool
    {
        $user = $this->userModel->where('email', $email)->first();

        if (!$user) {
            return false;
        }

        if (!password_verify($password, $user['password'])) {
            // 失敗回数のカウントと制限
            $this->incrementLoginAttempts($email);
            return false;
        }

        // セッションの再生成(セッション固定攻撃対策)
        $this->session->regenerate();
        
        $this->session->set([
            'user_id' => $user['id'],
            'user_email' => $user['email'],
            'isLoggedIn' => true,
            'last_activity' => time()
        ]);

        return true;
    }

    protected function incrementLoginAttempts(string $email): void
    {
        $attempts = cache()->get('login_attempts_' . md5($email)) ?? 0;
        cache()->save('login_attempts_' . md5($email), ++$attempts, 300);

        if ($attempts >= 5) {
            throw new \Exception('Too many login attempts. Please try again later.');
        }
    }

    public function check(): bool
    {
        if (!$this->session->get('isLoggedIn')) {
            return false;
        }

        // セッションの有効期限チェック
        $lastActivity = $this->session->get('last_activity');
        if (time() - $lastActivity > config('Session')->expiration) {
            $this->session->destroy();
            return false;
        }

        // セッションの更新
        $this->session->set('last_activity', time());
        return true;
    }

    public function logout(): void
    {
        $this->session->destroy();
    }
}
  1. 認証フィルターの実装
// app/Filters/AuthFilter.php
namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class AuthFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $auth = service('auth');
        
        if (!$auth->check()) {
            // APIリクエストの場合
            if ($request->isAJAX()) {
                return service('response')
                    ->setStatusCode(401)
                    ->setJSON(['error' => 'Unauthorized']);
            }
            
            // 通常のリクエストの場合
            return redirect()
                ->to('/login')
                ->with('error', 'ログインが必要です');
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
    }
}

これらのセキュリティ対策を実装することで、一般的な攻撃からアプリケーションを保護することができます。ただし、セキュリティは常に最新の脅威に対応する必要があるため、定期的な見直しと更新が重要です。

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

RESTful APIの構築方法

  1. RESTful APIコントローラーの実装
namespace App\Controllers\API;

use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\API\ResponseTrait;

class Products extends ResourceController
{
    use ResponseTrait;
    
    protected $modelName = 'App\Models\ProductModel';
    protected $format = 'json';

    // GET /api/products
    public function index()
    {
        $products = $this->model->findAll();
        return $this->respond($products);
    }

    // GET /api/products/{id}
    public function show($id = null)
    {
        $product = $this->model->find($id);
        
        if ($product === null) {
            return $this->failNotFound('商品が見つかりません');
        }

        return $this->respond($product);
    }

    // POST /api/products
    public function create()
    {
        $data = $this->request->getJSON();
        
        if (!$this->validate($this->model->validationRules)) {
            return $this->failValidationErrors($this->validator->getErrors());
        }

        $id = $this->model->insert($data);
        
        if ($id === false) {
            return $this->failServerError('商品の登録に失敗しました');
        }

        $product = $this->model->find($id);
        return $this->respondCreated($product);
    }

    // PUT /api/products/{id}
    public function update($id = null)
    {
        $data = $this->request->getJSON();
        
        if (!$this->model->find($id)) {
            return $this->failNotFound('更新対象の商品が見つかりません');
        }

        if (!$this->validate($this->model->validationRules)) {
            return $this->failValidationErrors($this->validator->getErrors());
        }

        if ($this->model->update($id, $data) === false) {
            return $this->failServerError('商品の更新に失敗しました');
        }

        return $this->respond(['message' => '商品が更新されました']);
    }

    // DELETE /api/products/{id}
    public function delete($id = null)
    {
        if (!$this->model->find($id)) {
            return $this->failNotFound('削除対象の商品が見つかりません');
        }

        if ($this->model->delete($id) === false) {
            return $this->failServerError('商品の削除に失敗しました');
        }

        return $this->respondDeleted(['message' => '商品が削除されました']);
    }
}
  1. APIフィルターの実装
namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

class APIAuthFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $apiKey = $request->getHeaderLine('X-API-Key');
        
        if (!$this->validateApiKey($apiKey)) {
            return service('response')
                ->setStatusCode(401)
                ->setJSON([
                    'status' => 401,
                    'error' => '無効なAPIキーです'
                ]);
        }
    }

    protected function validateApiKey($apiKey)
    {
        // APIキーの検証ロジック
        return true; // 実際の実装では適切な検証を行う
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
    }
}

キャッシュ機能の効果的な活用

  1. キャッシュの基本設定
// app/Config/Cache.php
public $handler = 'file';
public $backupHandler = 'dummy';
public $prefix = '';
public $ttl = 60;
public $reservedCharacters = '{}()/\@:';

// Redisハンドラーの設定例
public $redis = [
    'host' => '127.0.0.1',
    'password' => null,
    'port' => 6379,
    'timeout' => 0,
    'database' => 0,
];
  1. キャッシュの実践的な活用例
class ProductController extends BaseController
{
    public function index()
    {
        $cache = \Config\Services::cache();
        $cacheKey = 'products_list_' . md5(json_encode($this->request->getGet()));
        
        // キャッシュの取得を試みる
        if ($products = $cache->get($cacheKey)) {
            return $this->response->setJSON($products);
        }
        
        // キャッシュがない場合はデータを取得
        $products = $this->productModel
            ->select('products.*, categories.name as category_name')
            ->join('categories', 'categories.id = products.category_id')
            ->findAll();
            
        // キャッシュに保存(5分間)
        $cache->save($cacheKey, $products, 300);
        
        return $this->response->setJSON($products);
    }

    // キャッシュの手動クリア
    protected function clearProductCache()
    {
        $cache = \Config\Services::cache();
        $cache->deleteMatching('products_list_*');
    }
}

ユニットテストとデバッグの進め方

  1. テストクラスの実装
namespace App\Tests;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;

class ProductTest extends CIUnitTestCase
{
    use DatabaseTestTrait;
    use FeatureTestTrait;

    protected $refresh = true;
    protected $seed = 'ProductSeeder';

    public function testCreate()
    {
        $data = [
            'name' => 'テスト商品',
            'price' => 1000,
            'category_id' => 1,
            'stock' => 100
        ];

        $result = $this->withHeaders([
            'X-API-Key' => 'your-api-key'
        ])->post('api/products', $data);

        $result->assertStatus(201)
               ->assertJSONFragment(['name' => 'テスト商品']);
    }

    public function testInvalidCreate()
    {
        $data = [
            'name' => '', // 空の名前(バリデーションエラー)
            'price' => -100, // 負の価格
        ];

        $result = $this->withHeaders([
            'X-API-Key' => 'your-api-key'
        ])->post('api/products', $data);

        $result->assertStatus(400)
               ->assertJSONFragment(['errors']);
    }

    public function testUpdate()
    {
        $data = [
            'name' => '更新後の商品名',
            'price' => 2000
        ];

        $result = $this->withHeaders([
            'X-API-Key' => 'your-api-key'
        ])->put('api/products/1', $data);

        $result->assertStatus(200)
               ->assertJSONFragment(['message' => '商品が更新されました']);
    }
}
  1. デバッグツールの活用
// デバッグバーの設定
// app/Config/Toolbar.php
public $collectors = [
    \CodeIgniter\Debug\Toolbar\Collectors\Timers::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Database::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Logs::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Views::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Cache::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Files::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Routes::class,
    \CodeIgniter\Debug\Toolbar\Collectors\Events::class,
];

// コード内でのデバッグ
public function debugExample()
{
    // ログの出力
    log_message('debug', 'デバッグメッセージ: {value}', ['value' => $someValue]);
    
    // 変数の内容確認
    dd($variable); // die and dump
    
    // タイマーの使用
    $benchmark = \Config\Services::timer();
    $benchmark->start('my_timer');
    
    // 処理
    
    $benchmark->stop('my_timer');
    echo $benchmark->getElapsedTime('my_timer');
}

これらの実践的な開発手法を適切に活用することで、保守性が高く、効率的なアプリケーション開発が可能になります。特に、テストとデバッグを重視することで、品質の高いアプリケーションを提供することができます。

CodeIgniter 4でのパフォーマンス最適化

キャッシュ戦略とその実装方法

  1. 階層的キャッシュの実装
class ProductService
{
    protected $cache;
    protected $productModel;
    
    public function __construct()
    {
        $this->cache = \Config\Services::cache();
        $this->productModel = new \App\Models\ProductModel();
    }
    
    public function getProductDetails($productId)
    {
        // 第1層: メモリキャッシュ
        $cacheKey = "product_{$productId}";
        $product = $this->cache->get($cacheKey);
        
        if ($product === null) {
            // 第2層: Redisキャッシュ
            $redis = new \Redis();
            $redis->connect('127.0.0.1', 6379);
            $product = $redis->get($cacheKey);
            
            if ($product === null) {
                // 第3層: データベース
                $product = $this->productModel->find($productId);
                
                // キャッシュの更新
                if ($product) {
                    $redis->setex($cacheKey, 3600, serialize($product));
                    $this->cache->save($cacheKey, $product, 300);
                }
            } else {
                $product = unserialize($product);
                $this->cache->save($cacheKey, $product, 300);
            }
        }
        
        return $product;
    }
}
  1. ビューキャッシュの活用
class CatalogController extends BaseController
{
    public function categoryPage($categoryId)
    {
        $cacheKey = "category_page_{$categoryId}";
        
        if (! $output = cache($cacheKey)) {
            $data = [
                'products' => $this->productModel
                    ->where('category_id', $categoryId)
                    ->findAll(),
                'category' => $this->categoryModel->find($categoryId)
            ];
            
            $output = view('catalog/category', $data);
            
            // ビューをキャッシュに保存(1時間)
            cache()->save($cacheKey, $output, 3600);
        }
        
        return $output;
    }
}

データベースの最適化手法

  1. クエリの最適化
class OptimizedProductModel extends \CodeIgniter\Model
{
    public function getProductsWithEfficiency($categoryId)
    {
        return $this->select('products.*, 
                            categories.name as category_name,
                            COUNT(reviews.id) as review_count')
                    ->join('categories', 'categories.id = products.category_id')
                    ->join('reviews', 'reviews.product_id = products.id', 'left')
                    ->where('products.category_id', $categoryId)
                    ->groupBy('products.id')
                    ->having('review_count >', 0)
                    ->useIndex('idx_category') // インデックスの明示的な使用
                    ->findAll();
    }
    
    // バルクインサートの実装
    public function bulkInsertProducts($products, $batchSize = 100)
    {
        $chunks = array_chunk($products, $batchSize);
        
        $this->db->transStart();
        try {
            foreach ($chunks as $chunk) {
                $this->insertBatch($chunk);
            }
            $this->db->transComplete();
            return $this->db->transStatus();
        } catch (\Exception $e) {
            $this->db->transRollback();
            throw $e;
        }
    }
}
  1. インデックス最適化
-- 必要なインデックスの作成
CREATE INDEX idx_category ON products(category_id);
CREATE INDEX idx_price ON products(price);
CREATE INDEX idx_created_at ON products(created_at);
CREATE INDEX idx_status_category ON products(status, category_id);

-- 複合インデックスの作成
CREATE INDEX idx_category_price ON products(category_id, price);
  1. データベースコネクションの最適化
// app/Config/Database.php
public $default = [
    'hostname' => 'localhost',
    'username' => 'your_username',
    'password' => 'your_password',
    'database' => 'your_database',
    'DBDriver' => 'MySQLi',
    'DBPrefix' => '',
    'pConnect' => false,
    'DBDebug'  => true,
    'charset'  => 'utf8mb4',
    'DBCollat' => 'utf8mb4_unicode_ci',
    'swapPre'  => '',
    'encrypt'  => false,
    'compress' => false,
    'strictOn' => false,
    'failover' => [],
    'port'     => 3306,
    'persistent' => true,
    // コネクションプーリングの設定
    'pooling'  => true,
    'pool'     => [
        'max_connections' => 100,
        'idle_timeout'   => 60,
        'wait_timeout'   => 30,
    ],
];

本番環境でのパフォーマンスチューニング

  1. アプリケーション設定の最適化
// app/Config/App.php
public $baseURL = 'https://your-production-domain.com/';

// キャッシュの有効化
public $cache = 'file';

// エラー表示の無効化
public $displayErrors = false;

// ログレベルの調整
public $logThreshold = 3; // Error以上のみ記録

// セッション設定の最適化
public $sessionDriver = 'redis';
public $sessionExpiration = 7200;
  1. パフォーマンスモニタリングの実装
class PerformanceMonitor
{
    protected $timer;
    protected $logger;
    
    public function __construct()
    {
        $this->timer = \Config\Services::timer();
        $this->logger = \Config\Services::logger();
    }
    
    public function startMeasurement($point)
    {
        $this->timer->start($point);
    }
    
    public function endMeasurement($point)
    {
        $this->timer->stop($point);
        
        $elapsed = $this->timer->getElapsedTime($point);
        if ($elapsed > 1.0) { // 1秒以上かかった処理を記録
            $this->logger->warning("Performance Alert: {$point} took {$elapsed} seconds");
        }
        
        return $elapsed;
    }
    
    public function getMemoryUsage()
    {
        $memory = memory_get_usage(true);
        if ($memory > 64 * 1024 * 1024) { // 64MB以上使用している場合
            $this->logger->warning("High Memory Usage: {$memory} bytes");
        }
        return $memory;
    }
}

// 使用例
$monitor = new PerformanceMonitor();
$monitor->startMeasurement('database_query');
// データベースクエリの実行
$monitor->endMeasurement('database_query');
  1. 本番環境向けの.envファイル設定
# プロダクション環境設定
CI_ENVIRONMENT = production

# データベース設定
database.default.hostname = your_production_db_host
database.default.database = your_production_db
database.default.username = your_production_user
database.default.password = your_production_password
database.default.DBDriver = MySQLi
database.default.port = 3306

# キャッシュ設定
cache.handler = redis
cache.redis.host = your_redis_host
cache.redis.port = 6379
cache.redis.password = your_redis_password

# セッション設定
session.handler = redis
session.savePath = 'tcp://your_redis_host:6379'

# メール設定
email.protocol = smtp
email.SMTPHost = your_smtp_host
email.SMTPUser = your_smtp_user
email.SMTPPass = your_smtp_password
email.SMTPPort = 587
email.SMTPCrypto = tls

これらのパフォーマンス最適化テクニックを適切に組み合わせることで、アプリケーションの応答性と安定性を大幅に向上させることができます。ただし、最適化は必ずモニタリングと計測に基づいて行い、実際のパフォーマンス改善を確認しながら進めることが重要です。