【保存版】Laravel findメソッドの完全ガイド:8つの実践的な使い方とパフォーマンス最適化のコツ

Laravel findメソッドの基礎知識

Laravelのfindメソッドは、Eloquent ORMの中で最も基本的かつ重要なメソッドの1つです。このセクションでは、findメソッドの内部動作と基本的な使い方について詳しく解説します。

findメソッドが内部で実行していること

findメソッドは、モデルのプライマリーキーを使用してデータベースから単一のレコードを取得する際に使用されます。内部では以下のような処理が実行されています:

  1. クエリビルダーの生成
// findメソッドの内部実装(簡略化)
public function find($id, $columns = ['*'])
{
    if (is_array($id)) {
        return $this->findMany($id, $columns);
    }
    return $this->whereKey($id)->first($columns);
}
  1. SQL文の生成
// 生成されるSQL文の例
// SELECT * FROM users WHERE id = ? LIMIT 1
  1. キャッシュの確認(設定されている場合)
  2. データベースへのクエリ実行
  3. モデルインスタンスの生成

findメソッドとfindOrFailの違いと使い方

findメソッドとfindOrFailメソッドは、以下のような特徴と違いがあります:

findメソッド

// レコードが見つからない場合はnullを返す
$user = User::find(1);
if ($user === null) {
    // レコードが存在しない場合の処理
    return response()->json(['message' => 'ユーザーが見つかりません'], 404);
}

findOrFailメソッド

try {
    // レコードが見つからない場合はModelNotFoundExceptionをスロー
    $user = User::findOrFail(1);
} catch (ModelNotFoundException $e) {
    // 例外処理
    return response()->json(['message' => 'ユーザーが見つかりません'], 404);
}

findメソッドの主な特徴:

  • 単一のプライマリーキーまたはプライマリーキーの配列を受け取れる
  • レコードが存在しない場合はnullを返す
  • 明示的なエラーハンドリングが必要

findOrFailメソッドの主な特徴:

  • レコードが存在しない場合は例外をスロー
  • try-catch構文でのエラーハンドリングが可能
  • RESTful APIに適している

両メソッドの使い分け:

メソッド使用シーンエラーハンドリング戻り値
find存在チェックが必要な場合明示的な条件分岐Model|null
findOrFailRESTful API、存在が前提の場合例外処理Model

以上が、Laravelのfindメソッドの基本的な仕組みと使い方です。次のセクションでは、より実践的な使用方法について説明していきます。

Laravel findメソッドの実践的な使用方法

findメソッドを実際のアプリケーション開発で活用するための実践的な使用方法を説明します。

メインキーによる単一レコードの取得

基本的な使用方法から、より実践的なパターンまでを見ていきましょう:

// 基本的な使用方法
$user = User::find(1);

// 特定のカラムのみを取得
$userName = User::find(1, ['name', 'email']);

// リレーションを含めた取得
$userWithPosts = User::with('posts')->find(1);

// クロージャを使用したリレーションの条件付き取得
$userWithRecentPosts = User::with(['posts' => function($query) {
    $query->where('created_at', '>=', now()->subDays(7));
}])->find(1);

複数のメインキーを使用した複数レコードの取得

findメソッドは配列を渡すことで、複数のレコードを一度に取得できます:

// 複数のIDを指定して取得
$users = User::find([1, 2, 3]);

// 特定のカラムのみを取得
$users = User::find([1, 2, 3], ['id', 'name', 'email']);

// コレクションメソッドを使用した加工
$userNames = User::find([1, 2, 3])
    ->pluck('name')
    ->toArray();

// whereInとの違い
// findの場合(順序は保持される)
$users = User::find([3, 1, 2]); // 3, 1, 2の順で返される

// whereInの場合(データベースの順序)
$users = User::whereIn('id', [3, 1, 2])->get(); // IDの昇順で返される

カスタム書き込みと組み合わせた高度な検索

findメソッドをカスタムクエリと組み合わせることで、より柔軟な検索が可能になります:

// スコープとの組み合わせ
class User extends Model
{
    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }
}

// アクティブユーザーの中からID指定で取得
$activeUser = User::active()->find(1);

// グローバルスコープの一時的な解除
$user = User::withoutGlobalScope('active')->find(1);

// 複雑な条件との組み合わせ
$user = User::where(function($query) {
    $query->where('status', 'active')
          ->orWhere('role', 'admin');
})->find(1);

実装のベストプラクティス:

  1. リポジトリパターンでの実装例:
class UserRepository
{
    public function findWithCache($id)
    {
        return Cache::remember("user.{$id}", 3600, function() use ($id) {
            return User::find($id);
        });
    }

    public function findWithValidation($id)
    {
        $user = User::find($id);

        throw_if(!$user, new UserNotFoundException("User {$id} not found"));

        return $user;
    }
}
  1. サービスクラスでの実装例:
class UserService
{
    private $repository;

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

    public function getUserDetails($id)
    {
        $user = $this->repository->findWithCache($id);

        return [
            'user' => $user,
            'recent_activities' => $user->activities()->recent()->get(),
            'permissions' => $user->getAllPermissions()
        ];
    }
}

findメソッドを使用する際の重要なポイント:

使用パターンメリット注意点
単一レコード取得シンプルで直感的N+1問題に注意
複数レコード取得一括取得が可能メモリ使用量に注意
リレーション付き取得データの整合性が保証されるパフォーマンスインパクト
カスタムクエリ組み合わせ柔軟な検索が可能クエリの複雑化に注意

これらの実践的な使用方法を理解することで、findメソッドをより効果的に活用できます。次のセクションでは、エラーハンドリングについて詳しく説明していきます。

findメソッドのエラーハンドリング

findメソッドを使用する際の適切なエラーハンドリングは、アプリケーションの堅牢性を高める重要な要素です。このセクションでは、様々なエラーハンドリングのパターンと実装方法を説明します。

ModelNotFoundExceptionの適切な処理方法

ModelNotFoundExceptionは、findOrFailメソッドを使用した際にレコードが見つからない場合にスローされる例外です。以下に、様々な処理パターンを示します:

  1. 基本的な例外処理
use Illuminate\Database\Eloquent\ModelNotFoundException;

class UserController extends Controller
{
    public function show($id)
    {
        try {
            $user = User::findOrFail($id);
            return response()->json($user);
        } catch (ModelNotFoundException $e) {
            // ログ出力
            \Log::error('User not found:', [
                'id' => $id,
                'exception' => $e->getMessage()
            ]);

            // エラーレスポンス
            return response()->json([
                'message' => 'ユーザーが見つかりません',
                'error_code' => 'USER_NOT_FOUND'
            ], 404);
        }
    }
}
  1. グローバルな例外ハンドラーでの処理
// app/Exceptions/Handler.php
class Handler extends ExceptionHandler
{
    public function render($request, Throwable $exception)
    {
        if ($exception instanceof ModelNotFoundException) {
            $modelName = class_basename($exception->getModel());

            if ($request->expectsJson()) {
                return response()->json([
                    'message' => "{$modelName}が見つかりません",
                    'error_code' => strtoupper($modelName) . '_NOT_FOUND'
                ], 404);
            }

            // Web画面の場合
            return response()->view('errors.404', [
                'message' => "{$modelName}が見つかりません"
            ], 404);
        }

        return parent::render($request, $exception);
    }
}
  1. カスタム例外クラスの作成
// app/Exceptions/UserNotFoundException.php
class UserNotFoundException extends ModelNotFoundException
{
    protected $model = User::class;

    public function render($request)
    {
        return response()->json([
            'message' => 'ユーザーが見つかりません',
            'error_code' => 'USER_NOT_FOUND',
            'help_text' => '正しいユーザーIDを指定してください'
        ], 404);
    }
}

// 使用例
class UserController extends Controller
{
    public function show($id)
    {
        $user = User::find($id);

        if (!$user) {
            throw new UserNotFoundException;
        }

        return response()->json($user);
    }
}

findOrFailを使用した例外処理の実装

findOrFailメソッドを効果的に使用するためのパターンを紹介します:

  1. サービスクラスでの実装
class UserService
{
    public function getUserProfile($id)
    {
        try {
            $user = User::with(['profile', 'settings'])
                ->findOrFail($id);

            return [
                'status' => 'success',
                'data' => $user
            ];
        } catch (ModelNotFoundException $e) {
            return [
                'status' => 'error',
                'message' => 'ユーザープロフィールが見つかりません'
            ];
        }
    }
}
  1. ミドルウェアでの実装
// app/Http/Middleware/EnsureUserExists.php
class EnsureUserExists
{
    public function handle($request, Closure $next)
    {
        try {
            $user = User::findOrFail($request->route('user'));
            $request->merge(['user' => $user]);

            return $next($request);
        } catch (ModelNotFoundException $e) {
            return response()->json([
                'message' => 'ユーザーが見つかりません'
            ], 404);
        }
    }
}

エラーハンドリングのベストプラクティス:

パターン使用場面メリットデメリット
try-catch個別の処理が必要な場合きめ細かい制御が可能コードが冗長になる
グローバルハンドラー共通のエラー処理コードの重複を避けられるカスタマイズ性が低い
カスタム例外特定のモデル用の処理再利用性が高い実装コストが高い

エラーハンドリング時の重要なポイント:

  1. ログ出力の重要性
// エラー情報の詳細なログ出力
\Log::error('Record not found', [
    'model' => User::class,
    'id' => $id,
    'user_ip' => request()->ip(),
    'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
]);
  1. エラーメッセージの国際化
// resources/lang/ja/messages.php
return [
    'user_not_found' => 'ユーザーID :id が見つかりません',
];

// コントローラーでの使用
return response()->json([
    'message' => __('messages.user_not_found', ['id' => $id])
], 404);
  1. デバッグ情報の制御
// config/app.php のdebugの値に応じてエラー情報を制御
$errorResponse = [
    'message' => 'ユーザーが見つかりません',
    'error_code' => 'USER_NOT_FOUND'
];

if (config('app.debug')) {
    $errorResponse['debug'] = [
        'exception' => get_class($e),
        'trace' => $e->getTrace()
    ];
}

return response()->json($errorResponse, 404);

適切なエラーハンドリングを実装することで、アプリケーションの信頼性と保守性が向上します。次のセクションでは、findメソッドのパフォーマンス最適化について説明していきます。

findメソッドのパフォーマンス最適化

findメソッドを効率的に使用するためのパフォーマンス最適化技術について説明します。適切な最適化により、アプリケーションの応答性と scalability を大きく向上させることができます。

N+1問題の回避とEagerローディングの活用

N+1問題は、findメソッドを使用する際によく遭遇する性能問題です。以下に、その解決方法を示します:

  1. 基本的なEagerローディング
// N+1問題が発生するコード
$users = User::find([1, 2, 3]);
foreach ($users as $user) {
    echo $user->profile->bio; // 追加のクエリが実行される
}

// Eagerローディングを使用した最適化
$users = User::with('profile')->find([1, 2, 3]);
foreach ($users as $user) {
    echo $user->profile->bio; // 追加のクエリは実行されない
}
  1. 複数のリレーションのEagerローディング
// 複数のリレーションを一度に読み込む
$user = User::with(['profile', 'posts', 'comments'])
    ->find(1);

// ネストされたリレーションの読み込み
$user = User::with(['posts.comments', 'profile.settings'])
    ->find(1);

// 条件付きEagerローディング
$user = User::with(['posts' => function($query) {
    $query->where('published', true)
          ->orderBy('created_at', 'desc')
          ->limit(5);
}])->find(1);
  1. クエリカウントの監視
// デバッグ用のクエリログ設定
\DB::listen(function($query) {
    \Log::info(
        $query->sql,
        [
            'bindings' => $query->bindings,
            'time' => $query->time
        ]
    );
});

// クエリカウンターの実装
class QueryCounter
{
    private static $count = 0;

    public static function increment()
    {
        self::$count++;
    }

    public static function getCount()
    {
        return self::$count;
    }

    public static function reset()
    {
        self::$count = 0;
    }
}

// ミドルウェアでの使用
class QueryCounterMiddleware
{
    public function handle($request, Closure $next)
    {
        QueryCounter::reset();

        \DB::listen(function($query) {
            QueryCounter::increment();
        });

        $response = $next($request);

        // リクエスト完了時のクエリ数をログに記録
        \Log::info('Query count: ' . QueryCounter::getCount());

        return $response;
    }
}

キャッシュ戦略

findメソッドの結果をキャッシュすることで、データベースへのアクセスを削減できます:

  1. 基本的なキャッシュ実装
// シンプルなキャッシュ実装
$user = Cache::remember('user.' . $id, 3600, function() use ($id) {
    return User::find($id);
});

// タグ付きキャッシュ
$user = Cache::tags(['users', 'profile'])->remember(
    'user.' . $id,
    3600,
    function() use ($id) {
        return User::with('profile')->find($id);
    }
);
  1. モデルイベントと連動したキャッシュ制御
class User extends Model
{
    protected static function boot()
    {
        parent::boot();

        // モデル更新時にキャッシュをクリア
        static::updated(function ($user) {
            Cache::tags(['users'])->forget('user.' . $user->id);
        });

        // モデル削除時にキャッシュをクリア
        static::deleted(function ($user) {
            Cache::tags(['users'])->forget('user.' . $user->id);
        });
    }
}
  1. キャッシュリポジトリパターン
class CachedUserRepository implements UserRepositoryInterface
{
    private $cache;
    private $repository;

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

    public function find($id)
    {
        $cacheKey = 'user.' . $id;

        return $this->cache->remember(
            $cacheKey,
            3600,
            function() use ($id) {
                return $this->repository->find($id);
            }
        );
    }

    public function flush($id)
    {
        return $this->cache->forget('user.' . $id);
    }
}

パフォーマンス最適化のベストプラクティス:

最適化手法効果実装コスト適用シーン
Eager LoadingN+1問題の解消リレーション参照時
キャッシュ応答時間の短縮頻繁なアクセス時
クエリ監視パフォーマンス計測開発・デバッグ時

パフォーマンスモニタリングのヒント:

  1. クエリログの分析
// config/database.php
'mysql' => [
    'dump' => true,  // クエリをダンプ
    'log' => true,   // クエリをログに記録
]

// クエリの実行時間を計測
$start = microtime(true);
$user = User::find(1);
$time = microtime(true) - $start;
\Log::info("Query execution time: {$time}s");
  1. キャッシュヒット率の監視
class CacheMetrics
{
    public static function trackCacheHit($key)
    {
        Redis::incr('cache.hits');
        \Log::info("Cache hit: {$key}");
    }

    public static function trackCacheMiss($key)
    {
        Redis::incr('cache.misses');
        \Log::info("Cache miss: {$key}");
    }

    public static function getHitRate()
    {
        $hits = Redis::get('cache.hits') ?? 0;
        $misses = Redis::get('cache.misses') ?? 0;
        $total = $hits + $misses;

        return $total > 0 ? ($hits / $total) * 100 : 0;
    }
}

これらの最適化テクニックを適切に組み合わせることで、findメソッドを使用するアプリケーションのパフォーマンスを大幅に向上させることができます。次のセクションでは、実践的なユースケースについて説明していきます。

実践的なユースケース集

findメソッドの実践的な使用例を、具体的なシナリオに基づいて説明します。実際の開発現場でよく遭遇する状況とその解決方法を紹介します。

REST APIでのfindメソッドの活用例

  1. 基本的なRESTful APIの実装
class ProductController extends Controller
{
    public function show($id)
    {
        try {
            $product = Product::with(['category', 'tags'])
                ->findOrFail($id);

            return response()->json([
                'status' => 'success',
                'data' => new ProductResource($product)
            ]);
        } catch (ModelNotFoundException $e) {
            return response()->json([
                'status' => 'error',
                'message' => '商品が見つかりません'
            ], 404);
        }
    }

    // API Resource による整形
    class ProductResource extends JsonResource
    {
        public function toArray($request)
        {
            return [
                'id' => $this->id,
                'name' => $this->name,
                'price' => $this->price,
                'category' => new CategoryResource($this->whenLoaded('category')),
                'tags' => TagResource::collection($this->whenLoaded('tags')),
                'available' => $this->checkAvailability(),
                'created_at' => $this->created_at->toIso8601String()
            ];
        }
    }
}
  1. バルクオペレーションの実装
class BulkProductController extends Controller
{
    public function bulkShow(Request $request)
    {
        $productIds = $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'integer|exists:products,id'
        ]);

        $products = Product::with(['category', 'tags'])
            ->find($productIds['ids']);

        return ProductResource::collection($products);
    }
}

複雑な検索条件での使用例

  1. 条件付きリレーションを含む検索
class OrderController extends Controller
{
    public function findWithDetails($id)
    {
        $order = Order::with(['items' => function($query) {
            $query->where('status', 'active')
                  ->orderBy('price', 'desc');
        }, 'customer' => function($query) {
            $query->with(['address', 'paymentMethods']);
        }])->find($id);

        if (!$order) {
            throw new OrderNotFoundException($id);
        }

        // 合計金額の計算
        $order->total = $order->items->sum('price');

        // 配送ステータスの確認
        $order->shipping_status = $this->checkShippingStatus($order);

        return new OrderResource($order);
    }
}
  1. 複数の条件を組み合わせた検索
class ProductService
{
    public function findAvailableProduct($id)
    {
        $product = Product::where(function($query) {
            $query->where('status', 'active')
                  ->where('stock', '>', 0)
                  ->where('available_from', '<=', now());
        })->find($id);

        if (!$product) {
            throw new ProductNotAvailableException($id);
        }

        return $product;
    }
}

リレーションを含むモデルの検索例

  1. 深いネストを持つリレーションの取得
class CourseController extends Controller
{
    public function findWithAllRelations($id)
    {
        $course = Course::with([
            'instructor.profile',
            'modules' => function($query) {
                $query->orderBy('order');
            },
            'modules.lessons' => function($query) {
                $query->orderBy('order');
            },
            'modules.lessons.attachments',
            'students' => function($query) {
                $query->where('status', 'active');
            },
            'students.progress'
        ])->find($id);

        if (!$course) {
            return null;
        }

        // 進捗状況の計算
        $course->completion_rate = $this->calculateCompletionRate($course);

        // アクセス権のチェック
        $course->user_has_access = $this->checkUserAccess(auth()->user(), $course);

        return new CourseResource($course);
    }

    private function calculateCompletionRate($course)
    {
        $totalLessons = $course->modules->flatMap->lessons->count();
        $completedLessons = $course->students
            ->where('id', auth()->id())
            ->first()
            ->progress
            ->where('completed', true)
            ->count();

        return $totalLessons > 0 
            ? ($completedLessons / $totalLessons) * 100 
            : 0;
    }
}
  1. ポリモーフィックリレーションの処理
class CommentableController extends Controller
{
    public function findCommentable($type, $id)
    {
        $modelMap = [
            'post' => Post::class,
            'video' => Video::class,
            'product' => Product::class
        ];

        if (!isset($modelMap[$type])) {
            throw new InvalidCommentableTypeException($type);
        }

        $model = $modelMap[$type];

        $commentable = $model::with(['comments' => function($query) {
            $query->orderBy('created_at', 'desc')
                  ->with('user.profile');
        }])->find($id);

        if (!$commentable) {
            throw new ModelNotFoundException;
        }

        return new CommentableResource($commentable);
    }
}

実装時の重要なポイント:

ユースケース考慮すべき点推奨される実装方法
REST APIレスポンス形式の一貫性API Resourceの使用
複雑な検索パフォーマンスとメンテナンス性クエリスコープの活用
ネストされたリレーションN+1問題の防止適切なEager Loading

これらの実践的なユースケースを参考に、プロジェクトの要件に合わせた最適な実装を選択してください。次のセクションでは、findメソッドのベストプラクティスとヒントについて説明します。

findメソッドのベストプラクティスとヒント

findメソッドを効果的に活用するためのベストプラクティスと、実装時の重要なヒントを紹介します。

クリーンなコードを実現するためのパターン

  1. リポジトリパターンの実装
// インターフェースの定義
interface UserRepositoryInterface
{
    public function find($id);
    public function findOrFail($id);
    public function findMany(array $ids);
}

// 具象クラスの実装
class UserRepository implements UserRepositoryInterface
{
    private $model;
    private $cache;

    public function __construct(User $model, Cache $cache)
    {
        $this->model = $model;
        $this->cache = $cache;
    }

    public function find($id)
    {
        return $this->cache->remember(
            "user.{$id}",
            3600,
            fn() => $this->model->with(['profile', 'settings'])->find($id)
        );
    }

    public function findOrFail($id)
    {
        $user = $this->find($id);

        if (!$user) {
            throw new UserNotFoundException($id);
        }

        return $user;
    }

    public function findMany(array $ids)
    {
        return $this->model->with(['profile', 'settings'])->find($ids);
    }
}
  1. サービスレイヤーパターン
class UserService
{
    private $repository;
    private $logger;

    public function __construct(
        UserRepositoryInterface $repository,
        LoggerInterface $logger
    ) {
        $this->repository = $repository;
        $this->logger = $logger;
    }

    public function getUserDetails($id)
    {
        try {
            $user = $this->repository->findOrFail($id);

            return [
                'user' => $user,
                'permissions' => $this->getUserPermissions($user),
                'activity' => $this->getRecentActivity($user)
            ];
        } catch (UserNotFoundException $e) {
            $this->logger->error('User not found', ['id' => $id]);
            throw $e;
        }
    }

    private function getUserPermissions(User $user)
    {
        return Gate::inspect('view', $user)->allowed()
            ? $user->getAllPermissions()
            : [];
    }

    private function getRecentActivity(User $user)
    {
        return $user->activities()
            ->recent()
            ->limit(10)
            ->get();
    }
}
  1. 値オブジェクトの活用
class UserId
{
    private $value;

    public function __construct($value)
    {
        if (!is_int($value) || $value <= 0) {
            throw new InvalidArgumentException('Invalid user ID');
        }

        $this->value = $value;
    }

    public function getValue(): int
    {
        return $this->value;
    }
}

class UserRepository
{
    public function find(UserId $id)
    {
        return User::find($id->getValue());
    }
}

テストしやすいfindメソッドの実装方法

  1. モックを使用したテスト
class UserServiceTest extends TestCase
{
    public function testFindUserSuccess()
    {
        // リポジトリのモックを作成
        $repository = $this->createMock(UserRepositoryInterface::class);
        $repository->expects($this->once())
            ->method('findOrFail')
            ->with(1)
            ->willReturn(new User(['id' => 1, 'name' => 'Test User']));

        $service = new UserService($repository);
        $result = $service->getUserDetails(1);

        $this->assertEquals('Test User', $result['user']->name);
    }

    public function testFindUserNotFound()
    {
        $repository = $this->createMock(UserRepositoryInterface::class);
        $repository->expects($this->once())
            ->method('findOrFail')
            ->with(999)
            ->willThrow(new UserNotFoundException);

        $this->expectException(UserNotFoundException::class);

        $service = new UserService($repository);
        $service->getUserDetails(999);
    }
}
  1. テスト用のファクトリーの活用
class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'password' => Hash::make('password'),
        ];
    }

    public function withProfile()
    {
        return $this->afterCreating(function (User $user) {
            Profile::factory()->create(['user_id' => $user->id]);
        });
    }
}

class UserRepositoryTest extends TestCase
{
    public function testFindWithProfile()
    {
        $user = User::factory()
            ->withProfile()
            ->create();

        $repository = new UserRepository(new User);
        $foundUser = $repository->find($user->id);

        $this->assertNotNull($foundUser->profile);
    }
}

実装時のベストプラクティス:

パターンメリット使用シーン
リポジトリパターンデータアクセスの抽象化大規模アプリケーション
サービスレイヤービジネスロジックの分離複雑なドメインロジック
値オブジェクト型の安全性確保重要なドメイン概念

実装時の重要なヒント:

  1. 命名規則の統一
// 良い例
public function findActiveUser($id)
public function findByEmail($email)
public function findLatestOrder($userId)

// 避けるべき例
public function getUser($id)
public function searchByEmail($email)
public function fetchLatestOrder($userId)
  1. メソッドチェーンの活用
// クエリビルダーの流暢なインターフェース
$user = User::active()
    ->withProfile()
    ->withLastLogin()
    ->find($id);

// カスタムメソッドチェーン
$user = $userRepository
    ->withCache()
    ->withTracking()
    ->find($id);
  1. エラーメッセージの標準化
// エラーメッセージのテンプレート化
class ModelNotFoundMessage
{
    public static function create($model, $id): string
    {
        return sprintf(
            '%s with ID %s was not found',
            class_basename($model),
            $id
        );
    }
}

// 使用例
throw new ModelNotFoundException(
    ModelNotFoundMessage::create(User::class, $id)
);

findメソッドの実装において、これらのベストプラクティスとパターンを適切に組み合わせることで、保守性が高く、テストしやすいコードを実現できます。また、アプリケーションの規模や要件に応じて、最適なパターンを選択することが重要です。