1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

高度なLaravelテクニックを活用した家計管理アプリケーション開発ガイド

Last updated at Posted at 2024-11-04

はじめに

Laravelは、PHPのウェブアプリケーションフレームワークとして人気があり、多くの開発者に愛用されています。この記事では、家計管理アプリケーションを例に、Laravelの高度な機能について詳しく解説します。各章では、実際のコード例と詳細な説明を提供し、Laravelの高度な概念を理解しやすくします。

サンプルデータ

まず、以下の50件の家計データを使用して説明を進めていきます。このデータは、日付、カテゴリ、金額、メモの4つの列で構成されています。

$household_expenses = [
    ['date' => '2023-01-01', 'category' => '食費', 'amount' => 5000, 'memo' => '新年会'],
    ['date' => '2023-01-02', 'category' => '交通費', 'amount' => 1200, 'memo' => '通勤'],
    ['date' => '2023-01-03', 'category' => '光熱費', 'amount' => 8000, 'memo' => '電気代'],
    ['date' => '2023-01-04', 'category' => '日用品', 'amount' => 3000, 'memo' => '洗剤'],
    ['date' => '2023-01-05', 'category' => '食費', 'amount' => 4500, 'memo' => '夕食'],
    ['date' => '2023-01-06', 'category' => '交際費', 'amount' => 10000, 'memo' => '友人の誕生日'],
    ['date' => '2023-01-07', 'category' => '教育費', 'amount' => 15000, 'memo' => '参考書'],
    ['date' => '2023-01-08', 'category' => '医療費', 'amount' => 3000, 'memo' => '風邪薬'],
    ['date' => '2023-01-09', 'category' => '食費', 'amount' => 6000, 'memo' => '週末の買い物'],
    ['date' => '2023-01-10', 'category' => '趣味', 'amount' => 8000, 'memo' => 'コンサートチケット'],
    ['date' => '2023-01-11', 'category' => '交通費', 'amount' => 1000, 'memo' => 'タクシー'],
    ['date' => '2023-01-12', 'category' => '通信費', 'amount' => 7000, 'memo' => '携帯電話代'],
    ['date' => '2023-01-13', 'category' => '食費', 'amount' => 3500, 'memo' => 'ランチ'],
    ['date' => '2023-01-14', 'category' => '衣服費', 'amount' => 12000, 'memo' => '冬物セール'],
    ['date' => '2023-01-15', 'category' => '住居費', 'amount' => 80000, 'memo' => '家賃'],
    ['date' => '2023-01-16', 'category' => '食費', 'amount' => 4000, 'memo' => '外食'],
    ['date' => '2023-01-17', 'category' => '交通費', 'amount' => 1500, 'memo' => 'バス代'],
    ['date' => '2023-01-18', 'category' => '教育費', 'amount' => 20000, 'memo' => '塾代'],
    ['date' => '2023-01-19', 'category' => '日用品', 'amount' => 2500, 'memo' => 'トイレットペーパー'],
    ['date' => '2023-01-20', 'category' => '食費', 'amount' => 5500, 'memo' => '夕食会'],
    ['date' => '2023-01-21', 'category' => '趣味', 'amount' => 6000, 'memo' => '映画とポップコーン'],
    ['date' => '2023-01-22', 'category' => '光熱費', 'amount' => 6000, 'memo' => 'ガス代'],
    ['date' => '2023-01-23', 'category' => '食費', 'amount' => 3800, 'memo' => '昼食'],
    ['date' => '2023-01-24', 'category' => '交通費', 'amount' => 1300, 'memo' => '電車代'],
    ['date' => '2023-01-25', 'category' => '医療費', 'amount' => 5000, 'memo' => '歯医者'],
    ['date' => '2023-01-26', 'category' => '食費', 'amount' => 4200, 'memo' => '夕食'],
    ['date' => '2023-01-27', 'category' => '交際費', 'amount' => 8000, 'memo' => '同窓会'],
    ['date' => '2023-01-28', 'category' => '日用品', 'amount' => 1800, 'memo' => 'シャンプー'],
    ['date' => '2023-01-29', 'category' => '食費', 'amount' => 7000, 'memo' => '週末の買い物'],
    ['date' => '2023-01-30', 'category' => '趣味', 'amount' => 4500, 'memo' => '書籍'],
    ['date' => '2023-01-31', 'category' => '交通費', 'amount' => 1100, 'memo' => 'バス代'],
    ['date' => '2023-02-01', 'category' => '通信費', 'amount' => 5000, 'memo' => 'インターネット代'],
    ['date' => '2023-02-02', 'category' => '食費', 'amount' => 4800, 'memo' => '外食'],
    ['date' => '2023-02-03', 'category' => '衣服費', 'amount' => 9000, 'memo' => 'ジャケット'],
    ['date' => '2023-02-04', 'category' => '住居費', 'amount' => 5000, 'memo' => '修繕費'],
    ['date' => '2023-02-05', 'category' => '食費', 'amount' => 3700, 'memo' => 'ランチ'],
    ['date' => '2023-02-06', 'category' => '交通費', 'amount' => 1400, 'memo' => 'タクシー'],
    ['date' => '2023-02-07', 'category' => '教育費', 'amount' => 12000, 'memo' => 'オンライン講座'],
    ['date' => '2023-02-08', 'category' => '日用品', 'amount' => 2200, 'memo' => '掃除用品'],
    ['date' => '2023-02-09', 'category' => '食費', 'amount' => 5200, 'memo' => '夕食'],
    ['date' => '2023-02-10', 'category' => '趣味', 'amount' => 7500, 'memo' => 'スポーツ用品'],
    ['date' => '2023-02-11', 'category' => '光熱費', 'amount' => 7500, 'memo' => '水道代'],
    ['date' => '2023-02-12', 'category' => '食費', 'amount' => 4100, 'memo' => '昼食'],
    ['date' => '2023-02-13', 'category' => '交通費', 'amount' => 1600, 'memo' => '電車代'],
    ['date' => '2023-02-14', 'category' => '交際費', 'amount' => 15000, 'memo' => 'バレンタインデー'],
    ['date' => '2023-02-15', 'category' => '医療費', 'amount' => 4000, 'memo' => '薬代'],
    ['date' => '2023-02-16', 'category' => '食費', 'amount' => 4300, 'memo' => '夕食'],
    ['date' => '2023-02-17', 'category' => '趣味', 'amount' => 9000, 'memo' => 'ジム会費'],
    ['date' => '2023-02-18', 'category' => '日用品', 'amount' => 2800, 'memo' => '台所用品']
];

それでは、この家計データを使用して、Laravelの高度な機能を学んでいきましょう。

第1章: Eloquentモデルの高度な使用法

Eloquentは、Laravelのデータベース操作を簡単にするORMです。家計管理アプリケーションでは、支出を管理するためのEloquentモデルを作成します。

まず、Expenseモデルを作成しましょう。

php artisan make:model Expense -m

このコマンドは、モデルファイルとマイグレーションファイルを同時に作成します。次に、マイグレーションファイルを編集して、テーブル構造を定義します。

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateExpensesTable extends Migration
{
    public function up()
    {
        Schema::create('expenses', function (Blueprint $table) {
            $table->id();
            $table->date('date');
            $table->string('category');
            $table->integer('amount');
            $table->text('memo')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('expenses');
    }
}

次に、Expenseモデルを編集して、マスアサインメントを設定します。

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Expense extends Model
{
    use HasFactory;

    protected $fillable = ['date', 'category', 'amount', 'memo'];

    protected $casts = [
        'date' => 'date',
        'amount' => 'integer',
    ];
}

これで、基本的なモデルの設定が完了しました。次の章では、このモデルを使用してデータを操作する方法を学びます。

第2章: シーダーとファクトリーの活用

テストデータを簡単に生成するために、Laravelのシーダーとファクトリーを使用します。まず、ExpenseFactoryを作成しましょう。

php artisan make:factory ExpenseFactory

次に、ExpenseFactoryを編集して、ランダムなデータを生成するように設定します。

namespace Database\Factories;

use App\Models\Expense;
use Illuminate\Database\Eloquent\Factories\Factory;

class ExpenseFactory extends Factory
{
    protected $model = Expense::class;

    public function definition()
    {
        return [
            'date' => $this->faker->dateTimeBetween('-1 year', 'now'),
            'category' => $this->faker->randomElement(['食費', '交通費', '光熱費', '日用品', '交際費', '教育費', '医療費', '趣味', '衣服費', '住居費', '通信費']),
            'amount' => $this->faker->numberBetween(100, 100000),
            'memo' => $this->faker->sentence(),
        ];
    }
}

次に、シーダーを作成して、ファクトリーを使用してデータを生成します。

php artisan make:seeder ExpenseSeeder

ExpenseSeederを以下のように編集します。

namespace Database\Seeders;

use App\Models\Expense;
use Illuminate\Database\Seeder;

class ExpenseSeeder extends Seeder
{
    public function run()
    {
        Expense::factory()->count(100)->create();
    }
}

最後に、DatabaseSeederクラスを編集して、ExpenseSeederを呼び出します。

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(ExpenseSeeder::class);
    }
}

これで、php artisan db:seedコマンドを実行するだけで、100件のランダムな支出データを生成できます。次の章では、このデータを使用して高度なクエリを学びます。

第3章: 高度なEloquentクエリ

Eloquentを使用して、複雑なデータ取得や集計を行う方法を学びましょう。家計管理アプリケーションでは、カテゴリごとの支出合計や月ごとの支出推移など、様々な集計が必要になります。

まず、カテゴリごとの支出合計を取得するメソッドをExpenseモデルに追加します。

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class Expense extends Model
{
    use HasFactory;

    protected $fillable = ['date', 'category', 'amount', 'memo'];

    protected $casts = [
        'date' => 'date',
        'amount' => 'integer',
    ];

    public static function getTotalByCategory()
    {
        return self::select('category', DB::raw('SUM(amount) as total'))
            ->groupBy('category')
            ->orderBy('total', 'desc')
            ->get();
    }

    public static function getMonthlyTotal($year)
    {
        return self::select(DB::raw('MONTH(date) as month'), DB::raw('SUM(amount) as total'))
            ->whereYear('date', $year)
            ->groupBy(DB::raw('MONTH(date)'))
            ->orderBy('month')
            ->get();
    }
}

これらのメソッドを使用して、コントローラーでデータを取得し、ビューに渡すことができます。

第4章: リレーションシップの活用

家計管理アプリケーションをさらに発展させるために、ユーザーと支出のリレーションシップを設定しましょう。これにより、各ユーザーが自分の支出のみを管理できるようになります。

まず、UserモデルとExpenseモデルの間に1対多のリレーションシップを設定します。

Userモデルに以下のメソッドを追加します:

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    // 既存のコード...

    public function expenses()
    {
        return $this->hasMany(Expense::class);
    }
}

次に、Expenseモデルにuser_idカラムを追加し、Userモデルとのリレーションシップを設定します:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Expense extends Model
{
    use HasFactory;

    protected $fillable = ['date', 'category', 'amount', 'memo', 'user_id'];

    protected $casts = [
        'date' => 'date',
        'amount' => 'integer',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    // 既存のメソッド...
}

マイグレーションを作成してexpensesテーブルにuser_idカラムを追加します:

php artisan make:migration add_user_id_to_expenses_table --table=expenses

マイグレーションファイルを以下のように編集します:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddUserIdToExpensesTable extends Migration
{
    public function up()
    {
        Schema::table('expenses', function (Blueprint $table) {
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
        });
    }

    public function down()
    {
        Schema::table('expenses', function (Blueprint $table) {
            $table->dropForeign(['user_id']);
            $table->dropColumn('user_id');
        });
    }
}

これで、ユーザーと支出のリレーションシップが設定されました。次に、このリレーションシップを活用してデータを取得する方法を見ていきましょう。

第5章: スコープとローカルスコープの活用

Eloquentのスコープを使用すると、よく使用するクエリ条件をカプセル化できます。家計管理アプリケーションでは、特定の期間の支出を取得したり、特定のカテゴリの支出を取得したりする機能が必要です。

Expenseモデルに以下のスコープを追加しましょう:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class Expense extends Model
{
    use HasFactory;

    // 既存のコード...

    public function scopeThisMonth(Builder $query): Builder
    {
        return $query->whereMonth('date', now()->month)
                     ->whereYear('date', now()->year);
    }

    public function scopeCategory(Builder $query, string $category): Builder
    {
        return $query->where('category', $category);
    }

    public function scopeDateBetween(Builder $query, $start, $end): Builder
    {
        return $query->whereBetween('date', [$start, $end]);
    }
}

これらのスコープを使用すると、以下のようにクエリを簡潔に書くことができます:

// 今月の支出を取得
$thisMonthExpenses = Expense::thisMonth()->get();

// 食費カテゴリの支出を取得
$foodExpenses = Expense::category('食費')->get();

// 特定の期間の支出を取得
$periodExpenses = Expense::dateBetween('2023-01-01', '2023-12-31')->get();

第6章: イベントとオブザーバーの活用

Laravelのイベントとオブザーバーを使用すると、モデルの特定のアクションに応じて自動的に処理を実行できます。例えば、新しい支出が追加されたときに、ユーザーの月間予算を更新する処理を自動化できます。

まず、ExpenseObserverを作成します:

php artisan make:observer ExpenseObserver --model=Expense

ExpenseObserverを以下のように編集します:

namespace App\Observers;

use App\Models\Expense;
use App\Models\User;

class ExpenseObserver
{
    public function created(Expense $expense)
    {
        $user = User::find($expense->user_id);
        $user->updateMonthlyBudget();
    }

    public function updated(Expense $expense)
    {
        $user = User::find($expense->user_id);
        $user->updateMonthlyBudget();
    }

    public function deleted(Expense $expense)
    {
        $user = User::find($expense->user_id);
        $user->updateMonthlyBudget();
    }
}

次に、AppServiceProviderでオブザーバーを登録します:

namespace App\Providers;

use App\Models\Expense;
use App\Observers\ExpenseObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Expense::observe(ExpenseObserver::class);
    }
}

最後に、UserモデルにupdateMonthlyBudgetメソッドを追加します:

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    // 既存のコード...

    public function updateMonthlyBudget()
    {
        $thisMonthTotal = $this->expenses()
            ->whereMonth('date', now()->month)
            ->whereYear('date', now()->year)
            ->sum('amount');

        $this->monthly_budget = $thisMonthTotal;
        $this->save();
    }
}

これで、支出が追加、更新、削除されるたびに、ユーザーの月間予算が自動的に更新されます。

第7章: ポリシーとゲートの実装

Laravelのポリシーとゲートを使用して、ユーザーが自分の支出データのみにアクセスできるように制御しましょう。

まず、ExpensePolicyを作成します:

php artisan make:policy ExpensePolicy --model=Expense

ExpensePolicyを以下のように編集します:

namespace App\Policies;

use App\Models\Expense;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class ExpensePolicy
{
    use HandlesAuthorization;

    public function view(User $user, Expense $expense)
    {
        return $user->id === $expense->user_id;
    }

    public function update(User $user, Expense $expense)
    {
        return $user->id === $expense->user_id;
    }

    public function delete(User $user, Expense $expense)
    {
        return $user->id === $expense->user_id;
    }
}

次に、AuthServiceProviderでポリシーを登録します:

namespace App\Providers;

use App\Models\Expense;
use App\Policies\ExpensePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Expense::class => ExpensePolicy::class,
    ];

    public function boot()
    {
        $this->registerPolicies();

        Gate::define('manage-expenses', function (User $user) {
            return $user->is_admin;
        });
    }
}

これで、コントローラーやビューで以下のように認可チェックを行うことができます:

// コントローラーでの使用例
public function show(Expense $expense)
{
    $this->authorize('view', $expense);
    return view('expenses.show', compact('expense'));
}

// ビューでの使用例
@can('update', $expense)
    <a href="{{ route('expenses.edit', $expense) }}">編集</a>
@endcan

第8章: カスタムバリデーションルールの作成

家計管理アプリケーションでは、支出の金額が負の値にならないようにするなど、特定のバリデーションルールが必要になることがあります。カスタムバリデーションルールを作成して、これを実現しましょう。

まず、カスタムバリデーションルールを作成します:

php artisan make:rule PositiveAmount

PositiveAmountルールを以下のように編集します:

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class PositiveAmount implements Rule
{
    public function passes($attribute, $value)
    {
        return is_numeric($value) && $value > 0;
    }
    
    public function message()
    {
        return ':attributeは正の数値である必要があります。';
    }
}

このカスタムルールをExpenseControllerで使用します:

namespace App\Http\Controllers;

use App\Models\Expense;
use App\Rules\PositiveAmount;
use Illuminate\Http\Request;

class ExpenseController extends Controller
{
    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'date' => 'required|date',
            'category' => 'required|string',
            'amount' => ['required', 'numeric', new PositiveAmount],
            'memo' => 'nullable|string',
        ]);

        $expense = auth()->user()->expenses()->create($validatedData);

        return redirect()->route('expenses.index')->with('success', '支出が追加されました。');
    }

    // 他のメソッド...
}

第9章: ミドルウェアの活用

ミドルウェアを使用して、特定のルートやルートグループに対して前処理や後処理を行うことができます。例えば、管理者のみがアクセスできる機能を作成する場合、カスタムミドルウェアを使用できます。

まず、管理者チェックのミドルウェアを作成します:

php artisan make:middleware CheckAdmin

CheckAdminミドルウェアを以下のように編集します:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckAdmin
{
    public function handle(Request $request, Closure $next)
    {
        if (!auth()->check() || !auth()->user()->is_admin) {
            abort(403, '管理者権限が必要です。');
        }

        return $next($request);
    }
}

次に、app/Http/Kernel.phpファイルにこのミドルウェアを登録します:

protected $routeMiddleware = [
    // 既存のミドルウェア...
    'admin' => \App\Http\Middleware\CheckAdmin::class,
];

これで、ルートファイルで以下のようにミドルウェアを使用できます:

Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'dashboard']);
    Route::get('/admin/users', [AdminController::class, 'users']);
});

第10章: キャッシュの活用

家計管理アプリケーションのパフォーマンスを向上させるために、Laravelのキャッシュ機能を活用しましょう。例えば、月間の支出合計など、頻繁に計算される値をキャッシュすることができます。

ExpenseControllerにキャッシュを実装してみましょう:

namespace App\Http\Controllers;

use App\Models\Expense;
use Illuminate\Support\Facades\Cache;

class ExpenseController extends Controller
{
    public function monthlyTotal()
    {
        $userId = auth()->id();
        $cacheKey = "monthly_total_{$userId}_" . date('Ym');

        return Cache::remember($cacheKey, now()->addHours(1), function () use ($userId) {
            return Expense::where('user_id', $userId)
                ->whereMonth('date', now()->month)
                ->whereYear('date', now()->year)
                ->sum('amount');
        });
    }

    // 他のメソッド...
}

この例では、ユーザーごとの月間支出合計を1時間キャッシュします。キャッシュキーにはユーザーIDと年月を含めることで、ユーザーごと、月ごとに異なるキャッシュを生成します。

第11章: ジョブとキューの活用

大量のデータ処理や時間のかかる処理は、バックグラウンドジョブとして実行することで、ユーザーエクスペリエンスを向上させることができます。例えば、月次レポートの生成をバックグラウンドジョブとして実装してみましょう。

まず、ジョブを作成します:

php artisan make:job GenerateMonthlyReport

GenerateMonthlyReportジョブを以下のように編集します:

namespace App\Jobs;

use App\Models\User;
use App\Models\Expense;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class GenerateMonthlyReport implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;
    protected $year;
    protected $month;

    public function __construct(User $user, $year, $month)
    {
        $this->user = $user;
        $this->year = $year;
        $this->month = $month;
    }

    public function handle()
    {
        $expenses = Expense::where('user_id', $this->user->id)
            ->whereYear('date', $this->year)
            ->whereMonth('date', $this->month)
            ->get();

        $total = $expenses->sum('amount');
        $categoryTotals = $expenses->groupBy('category')
            ->map(function ($items) {
                return $items->sum('amount');
            });

        // レポートの生成と保存のロジックをここに実装
        // 例: PDFの生成、データベースへの保存、メール送信など
    }
}

このジョブを以下のように使用します:

use App\Jobs\GenerateMonthlyReport;

// コントローラーやコマンドなどで
GenerateMonthlyReport::dispatch($user, 2023, 11);

第12章: イベントとリスナーの活用

イベントとリスナーを使用すると、アプリケーションの様々な部分を疎結合に保ちながら、特定のアクションに応じて処理を実行できます。例えば、ユーザーが予算を超過したときに通知を送る機能を実装してみましょう。

まず、イベントを作成します:

php artisan make:event BudgetExceeded

BudgetExceededイベントを以下のように編集します:

namespace App\Events;

use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class BudgetExceeded
{
    use Dispatchable, SerializesModels;

    public $user;
    public $amount;

    public function __construct(User $user, $amount)
    {
        $this->user = $user;
        $this->amount = $amount;
    }
}

次に、このイベントのリスナーを作成します:

php artisan make:listener SendBudgetExceededNotification

SendBudgetExceededNotificationリスナーを以下のように編集します:

namespace App\Listeners;

use App\Events\BudgetExceeded;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Notification;
use App\Notifications\BudgetExceededNotification;

class SendBudgetExceededNotification implements ShouldQueue
{
    public function handle(BudgetExceeded $event)
    {
        Notification::send($event->user, new BudgetExceededNotification($event->amount));
    }
}

最後に、EventServiceProviderでイベントとリスナーを登録します:

protected $listen = [
    BudgetExceeded::class => [
        SendBudgetExceededNotification::class,
    ],
];

これで、支出が追加されたときに予算超過をチェックし、必要に応じてイベントを発火させることができます:

public function addExpense(Request $request)
{
    // 支出の追加処理

    $totalExpenses = $user->expenses()->whereMonth('date', now()->month)->sum('amount');
    if ($totalExpenses > $user->monthly_budget) {
        event(new BudgetExceeded($user, $totalExpenses - $user->monthly_budget));
    }
}

第13章: アクセサとミューテータの活用

Eloquentモデルのアクセサとミューテータを使用すると、データベースの値を取得または設定する際に自動的に変換を行うことができます。例えば、金額を常に整数で保存し、表示時に小数点以下2桁まで表示する機能を実装してみましょう。

Expenseモデルに以下のアクセサとミューテータを追加します:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Expense extends Model
{
    // 既存のコード...

    public function getFormattedAmountAttribute()
    {
        return number_format($this->amount / 100, 2);
    }

    public function setAmountAttribute($value)
    {
        $this->attributes['amount'] = $value * 100;
    }
}

これにより、以下のように使用できます:

$expense = new Expense();
$expense->amount = 10.50; // データベースには1050として保存される

echo $expense->formatted_amount; // "10.50"と表示される

第14章: サービスコンテナとサービスプロバイダの活用

Laravelのサービスコンテナを使用すると、クラスの依存関係を管理し、アプリケーション全体で再利用可能なサービスを作成できます。例えば、為替レート計算サービスを作成し、サービスプロバイダを通じてアプリケーション全体で使用できるようにしましょう。

まず、為替レート計算サービスを作成します:

namespace App\Services;

class CurrencyConverter
{
    protected $rates;

    public function __construct()
    {
        // 実際のアプリケーションでは、APIから最新のレートを取得するなどの処理を行います
        $this->rates = [
            'USD' => 1,
            'EUR' => 0.85,
            'JPY' => 110,
        ];
    }

    public function convert($amount, $from, $to)
    {
        $fromRate = $this->rates[$from];
        $toRate = $this->rates[$to];

        return ($amount / $fromRate) * $toRate;
    }
}

次に、このサービスを登録するサービスプロバイダを作成します:

php artisan make:provider CurrencyServiceProvider

CurrencyServiceProviderを以下のように編集します:

namespace App\Providers;

use App\Services\CurrencyConverter;
use Illuminate\Support\ServiceProvider;

class CurrencyServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(CurrencyConverter::class, function ($app) {
            return new CurrencyConverter();
        });
    }
}

config/app.phpにこのサービスプロバイダを登録します:

'providers' => [
    // 他のプロバイダ...
    App\Providers\CurrencyServiceProvider::class,
],

これで、アプリケーション全体でCurrencyConverterサービスを使用できます:

namespace App\Http\Controllers;

use App\Services\CurrencyConverter;

class ExpenseController extends Controller
{
    public function show(Expense $expense, CurrencyConverter $converter)
    {
        $amountInUSD = $converter->convert($expense->amount, 'JPY', 'USD');

        return view('expenses.show', compact('expense', 'amountInUSD'));
    }
}

第15章: テストの実装

最後に、アプリケーションの信頼性を確保するためにテストを実装しましょう。Laravelは、PHPUnitを使用した強力なテスト機能を提供しています。

まず、Expenseモデルのテストを作成します:

php artisan make:test Models/ExpenseTest

ExpenseTestを以下のように編集します:

namespace Tests\Unit\Models;

use App\Models\Expense;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExpenseTest extends TestCase
{
    use RefreshDatabase;

    public function test_expense_belongs_to_user()
    {
        $user = User::factory()->create();
        $expense = Expense::factory()->create(['user_id' => $user->id]);

        $this->assertInstanceOf(User::class, $expense->user);
        $this->assertEquals($user->id, $expense->user->id);
    }

    public function test_formatted_amount_accessor()
    {
        $expense = Expense::factory()->create(['amount' => 10000]); // 100.00

        $this->assertEquals('100.00', $expense->formatted_amount);
    }

    public function test_amount_mutator()
    {
        $expense = new Expense();
        $expense->amount = 10.50;

        $this->assertEquals(1050, $expense->getAttributes()['amount']);
    }
}

次に、ExpenseControllerの機能テストを作成します:

php artisan make:test Http/Controllers/ExpenseControllerTest

ExpenseControllerTestを以下のように編集します:

namespace Tests\Feature\Http\Controllers;

use App\Models\User;
use App\Models\Expense;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExpenseControllerTest extends TestCase
{
    use RefreshDatabase;

    public function test_index_displays_expenses()
    {
        $user = User::factory()->create();
        $expense = Expense::factory()->create(['user_id' => $user->id]);

        $response = $this->actingAs($user)->get(route('expenses.index'));

        $response->assertStatus(200);
        $response->assertSee($expense->formatted_amount);
    }

    public function test_store_creates_new_expense()
    {
        $user = User::factory()->create();
        $expenseData = [
            'date' => '2023-11-01',
            'category' => '食費',
            'amount' => 1000,
            'memo' => 'テスト支出'
        ];

        $response = $this->actingAs($user)->post(route('expenses.store'), $expenseData);

        $response->assertRedirect(route('expenses.index'));
        $this->assertDatabaseHas('expenses', [
            'user_id' => $user->id,
            'category' => '食費',
            'amount' => 100000, // 金額は100倍で保存されることを確認
        ]);
    }

    public function test_update_modifies_expense()
    {
        $user = User::factory()->create();
        $expense = Expense::factory()->create(['user_id' => $user->id]);
        $updatedData = [
            'date' => '2023-11-02',
            'category' => '交通費',
            'amount' => 1500,
            'memo' => '更新されたメモ'
        ];

        $response = $this->actingAs($user)->put(route('expenses.update', $expense), $updatedData);

        $response->assertRedirect(route('expenses.index'));
        $this->assertDatabaseHas('expenses', [
            'id' => $expense->id,
            'category' => '交通費',
            'amount' => 150000, // 金額は100倍で保存されることを確認
        ]);
    }

    public function test_destroy_deletes_expense()
    {
        $user = User::factory()->create();
        $expense = Expense::factory()->create(['user_id' => $user->id]);

        $response = $this->actingAs($user)->delete(route('expenses.destroy', $expense));

        $response->assertRedirect(route('expenses.index'));
        $this->assertDatabaseMissing('expenses', ['id' => $expense->id]);
    }
}

これらのテストは、モデルの関係、アクセサ、ミューテータ、そしてコントローラーの主要な機能をカバーしています。テストを実行するには、以下のコマンドを使用します:

php artisan test

以上で、Laravelを使用した高度な家計管理アプリケーションの主要な機能と概念をカバーしました。この記事では、Eloquentモデル、リレーションシップ、スコープ、イベントとオブザーバー、ポリシーとゲート、カスタムバリデーション、ミドルウェア、キャッシュ、ジョブとキュー、イベントとリスナー、アクセサとミューテータ、サービスコンテナとサービスプロバイダ、そしてテストの実装について説明しました。

これらの機能を組み合わせることで、堅牢で拡張性の高い家計管理アプリケーションを構築することができます。実際のアプリケーション開発では、ユーザーインターフェース、セキュリティ、パフォーマンス最適化など、さらに多くの側面を考慮する必要がありますが、この記事で説明した概念は、高度なLaravelアプリケーションを開発する上での強固な基盤となるでしょう。

Laravelの学習を続け、公式ドキュメントを参照しながら、実際のプロジェクトでこれらの概念を適用していくことをお勧めします。エラー処理、ログ記録、APIの構築など、さらに高度なトピックにも挑戦してみてください。継続的な学習と実践を通じて、より複雑で洗練されたアプリケーションを開発する能力を身につけることができるでしょう。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?