Laravel

PHPで最も人気の高いフルスタックWebフレームワーク。Eloquent ORMやBladeテンプレートエンジンを搭載し、開発効率性に優れている。

PHPフレームワークBackendWebEloquentArtisanMVC

GitHub概要

laravel/laravel

Laravel is a web application framework with expressive, elegant syntax. We’ve already laid the foundation for your next big idea — freeing you to create without sweating the small things.

スター81,516
ウォッチ4,403
フォーク24,421
作成日:2011年6月8日
言語:Blade
ライセンス:-

トピックス

frameworklaravelphp

スター履歴

laravel/laravel Star History
データ取得日時: 2025/7/16 08:43

フレームワーク

Laravel

概要

Laravelは、PHPの現代的なWebアプリケーションフレームワークで、エレガントな構文と豊富な機能により、高品質なWeb開発を可能にします。

詳細

Laravel(ララベル)は2011年にTaylor Otwellによって開発されたPHPのWebアプリケーションフレームワークです。「The PHP Framework for Web Artisans」をキャッチフレーズとし、美しく表現力豊かな構文を重視して設計されています。Model-View-Controller(MVC)アーキテクチャパターンを採用し、Eloquent ORM、Blade テンプレートエンジン、Artisan コマンドラインツール、ミドルウェア、依存性注入コンテナなどの強力な機能を提供します。認証、ルーティング、セッション、キャッシング、データベースマイグレーション、ユニットテストなどの機能が標準で含まれており、迅速な開発を可能にします。Composerを使用したパッケージ管理により、豊富なライブラリエコシステムを活用できます。現在ではDisney+、Toyota、BMW、Pfizer等の大企業でも採用され、PHPフレームワークの中で最も人気の高いフレームワークの一つとなっています。

メリット・デメリット

メリット

  • エレガントな構文: 美しく読みやすいコードが書ける
  • 豊富な標準機能: 認証、メール、キューなど多くの機能が内蔵
  • Eloquent ORM: 直感的で強力なデータベース操作
  • Artisan CLI: 開発効率を向上させるコマンドラインツール
  • Blade テンプレート: シンプルで強力なテンプレートエンジン
  • 活発なコミュニティ: 大規模なコミュニティと豊富な学習リソース
  • 優秀なドキュメント: 分かりやすく詳細な公式ドキュメント

デメリット

  • 学習コスト: 多機能ゆえに初心者には習得が困難
  • パフォーマンス: 他のPHPフレームワークと比較して若干重い
  • バージョン互換性: メジャーアップデートでの破壊的変更
  • メモリ使用量: 大規模アプリケーションでのメモリ消費が大きい
  • 過度な抽象化: シンプルなタスクでも複雑になる場合がある

主要リンク

書き方の例

Hello World

// プロジェクト作成コマンド
// composer create-project laravel/laravel hello-laravel
// cd hello-laravel
// php artisan serve

// routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\WelcomeController;

// 基本的なルート
Route::get('/', function () {
    return view('welcome', ['message' => 'Hello, Laravel World!']);
});

// 名前付きルート
Route::get('/hello', function () {
    return response()->json([
        'message' => 'Hello from Laravel API!',
        'timestamp' => now()->toDateTimeString(),
        'version' => app()->version()
    ]);
})->name('api.hello');

// コントローラーを使用したルート
Route::get('/welcome/{name?}', [WelcomeController::class, 'show'])
      ->name('welcome.show');

// app/Http/Controllers/WelcomeController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class WelcomeController extends Controller
{
    public function show(Request $request, $name = null)
    {
        $name = $name ?? 'Guest';
        
        return view('welcome.show', [
            'name' => $name,
            'time' => now()->format('Y年m月d日 H:i:s')
        ]);
    }
}

// resources/views/welcome/show.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laravel Welcome</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-8">
        <div class="bg-white rounded-lg shadow-md p-6">
            <h1 class="text-3xl font-bold text-gray-800 mb-4">
                Welcome, {{ $name }}!
            </h1>
            <p class="text-gray-600 mb-4">現在時刻: {{ $time }}</p>
            <a href="{{ route('api.hello') }}" 
               class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
                API エンドポイント
            </a>
        </div>
    </div>
</body>
</html>

Eloquent ORM

// モデル生成コマンド
// php artisan make:model User --migration --factory --seeder
// php artisan make:model Post --migration --factory
// php artisan make:model Tag --migration --factory
// php artisan make:model PostTag --migration

// app/Models/User.php
<?php

namespace App\Models;

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

class User extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'name', 'email', 'password', 'active'
    ];

    protected $hidden = [
        'password', 'remember_token'
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'active' => 'boolean'
    ];

    // リレーションシップ
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function publishedPosts()
    {
        return $this->hasMany(Post::class)->where('published', true);
    }

    // スコープ
    public function scopeActive($query)
    {
        return $query->where('active', true);
    }

    public function scopeHasPosts($query)
    {
        return $query->whereHas('posts');
    }

    // アクセサ
    public function getFullNameAttribute()
    {
        return "{$this->first_name} {$this->last_name}";
    }

    // ミューテータ
    public function setEmailAttribute($value)
    {
        $this->attributes['email'] = strtolower($value);
    }
}

// app/Models/Post.php
<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title', 'content', 'published', 'published_at', 'user_id'
    ];

    protected $casts = [
        'published' => 'boolean',
        'published_at' => 'datetime'
    ];

    // リレーションシップ
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'post_tags');
    }

    // スコープ
    public function scopePublished($query)
    {
        return $query->where('published', true);
    }

    public function scopeRecent($query)
    {
        return $query->orderBy('created_at', 'desc');
    }

    public function scopeByTag($query, $tagName)
    {
        return $query->whereHas('tags', function ($q) use ($tagName) {
            $q->where('name', $tagName);
        });
    }

    // アクセサ
    public function getExcerptAttribute()
    {
        return substr(strip_tags($this->content), 0, 150) . '...';
    }
}

// Eloquentの使用例
use App\Models\User;
use App\Models\Post;
use App\Models\Tag;

// データ作成
$user = User::create([
    'name' => '太郎',
    'email' => '[email protected]',
    'password' => bcrypt('password'),
    'active' => true
]);

$post = $user->posts()->create([
    'title' => 'Laravel入門',
    'content' => 'Laravelは素晴らしいフレームワークです。',
    'published' => true,
    'published_at' => now()
]);

$tag = Tag::create(['name' => 'Laravel']);
$post->tags()->attach($tag);

// クエリ例
$users = User::active()->with('posts')->get();
$posts = Post::published()
    ->with(['user', 'tags'])
    ->recent()
    ->paginate(10);

$laravelPosts = Post::byTag('Laravel')
    ->published()
    ->get();

// 高度なクエリ
$popularUsers = User::withCount(['posts' => function ($query) {
    $query->where('published', true);
}])
->having('posts_count', '>', 5)
->get();

// 一括更新
Post::where('created_at', '<', now()->subDays(30))
    ->update(['archived' => true]);

// リレーションの作成
$user = User::find(1);
$user->posts()->create([
    'title' => '新しい投稿',
    'content' => '内容...'
]);

ルーティング

// routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
use App\Http\Controllers\AdminController;
use App\Http\Controllers\Auth\LoginController;

// 基本的なルート
Route::get('/', function () {
    return view('home');
})->name('home');

Route::get('/about', function () {
    return view('about');
})->name('about');

// パラメータ付きルート
Route::get('/posts/{id}', function ($id) {
    $post = App\Models\Post::findOrFail($id);
    return view('posts.show', compact('post'));
})->name('posts.show')->where('id', '[0-9]+');

// オプショナルパラメータ
Route::get('/users/{id?}', function ($id = null) {
    if ($id) {
        $user = App\Models\User::findOrFail($id);
        return view('users.show', compact('user'));
    }
    return view('users.index');
})->name('users.index');

// RESTfulリソースルート
Route::resource('posts', PostController::class);

// 部分的なリソースルート
Route::resource('comments', CommentController::class)
    ->only(['index', 'store', 'destroy']);

// ネストしたリソース
Route::resource('posts.comments', CommentController::class)
    ->shallow();

// ルートグループ(ミドルウェア)
Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');

    Route::resource('posts', PostController::class)
        ->except(['index', 'show']);
});

// プレフィックス付きグループ
Route::prefix('admin')->name('admin.')->group(function () {
    Route::get('/dashboard', [AdminController::class, 'dashboard'])
        ->name('dashboard');
    Route::resource('users', AdminController::class);
});

// サブドメインルーティング
Route::domain('{subdomain}.example.com')->group(function () {
    Route::get('/', function ($subdomain) {
        return "Subdomain: {$subdomain}";
    });
});

// routes/api.php
Route::prefix('v1')->name('api.v1.')->group(function () {
    // 認証不要のAPI
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/posts/{post}', [PostController::class, 'show']);

    // 認証必要のAPI
    Route::middleware('auth:sanctum')->group(function () {
        Route::post('/posts', [PostController::class, 'store']);
        Route::put('/posts/{post}', [PostController::class, 'update']);
        Route::delete('/posts/{post}', [PostController::class, 'destroy']);
    });
});

// ルートモデルバインディング
Route::get('/posts/{post}', function (App\Models\Post $post) {
    return $post;
});

// カスタムキーでのモデルバインディング
Route::get('/posts/{post:slug}', function (App\Models\Post $post) {
    return $post;
});

// ルートキャッシュ
// php artisan route:cache
// php artisan route:clear

マイグレーションとシーダー

// マイグレーション生成コマンド
// php artisan make:migration create_posts_table
// php artisan make:migration add_published_to_posts_table --table=posts

// database/migrations/xxx_create_users_table.php
<?php

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

return new class extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->boolean('active')->default(true);
            $table->rememberToken();
            $table->timestamps();
            $table->softDeletes();

            $table->index(['active', 'created_at']);
        });
    }

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

// database/migrations/xxx_create_posts_table.php
<?php

return new class extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->string('slug')->unique();
            $table->boolean('published')->default(false);
            $table->timestamp('published_at')->nullable();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->timestamps();

            $table->index(['published', 'published_at']);
            $table->index('slug');
        });
    }

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

// カラム追加のマイグレーション
<?php

return new class extends Migration
{
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->integer('view_count')->default(0)->after('published_at');
            $table->json('meta_data')->nullable()->after('view_count');
            $table->index('view_count');
        });
    }

    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropIndex(['view_count']);
            $table->dropColumn(['view_count', 'meta_data']);
        });
    }
};

// database/seeders/UserSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder
{
    public function run()
    {
        // 管理者ユーザー作成
        User::create([
            'name' => 'Administrator',
            'email' => '[email protected]',
            'password' => Hash::make('password'),
            'active' => true
        ]);

        // ファクトリーを使用したダミーデータ生成
        User::factory(50)->create();
    }
}

// database/factories/PostFactory.php
<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class PostFactory extends Factory
{
    public function definition()
    {
        $title = $this->faker->sentence();
        
        return [
            'title' => $title,
            'slug' => Str::slug($title),
            'content' => $this->faker->paragraphs(3, true),
            'published' => $this->faker->boolean(70),
            'published_at' => $this->faker->optional(0.7)->dateTimeBetween('-1 year', 'now'),
            'user_id' => User::factory(),
        ];
    }

    public function published()
    {
        return $this->state([
            'published' => true,
            'published_at' => now()
        ]);
    }

    public function draft()
    {
        return $this->state([
            'published' => false,
            'published_at' => null
        ]);
    }
}

// マイグレーション実行コマンド
// php artisan migrate
// php artisan migrate:fresh --seed
// php artisan migrate:rollback
// php artisan migrate:status
// php artisan db:seed
// php artisan db:seed --class=UserSeeder

フォーム処理とバリデーション

// FormRequest生成コマンド
// php artisan make:request StorePostRequest

// app/Http/Requests/StorePostRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize()
    {
        return auth()->check();
    }

    public function rules()
    {
        return [
            'title' => 'required|string|max:255|unique:posts,title',
            'content' => 'required|string|min:50',
            'published' => 'boolean',
            'published_at' => 'nullable|date|after_or_equal:today',
            'tags' => 'array',
            'tags.*' => 'exists:tags,id',
            'featured_image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048'
        ];
    }

    public function messages()
    {
        return [
            'title.required' => 'タイトルは必須です。',
            'title.unique' => 'このタイトルは既に使用されています。',
            'content.required' => '内容は必須です。',
            'content.min' => '内容は50文字以上で入力してください。',
            'featured_image.image' => '画像ファイルを選択してください。',
            'featured_image.max' => '画像サイズは2MB以下にしてください。'
        ];
    }

    public function attributes()
    {
        return [
            'title' => 'タイトル',
            'content' => '内容',
            'published' => '公開設定',
            'published_at' => '公開日時',
            'tags' => 'タグ',
            'featured_image' => 'アイキャッチ画像'
        ];
    }
}

// app/Http/Controllers/PostController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with(['user', 'tags'])
            ->published()
            ->latest()
            ->paginate(10);

        return view('posts.index', compact('posts'));
    }

    public function show(Post $post)
    {
        $post->load(['user', 'tags']);
        return view('posts.show', compact('post'));
    }

    public function create()
    {
        $tags = Tag::all();
        return view('posts.create', compact('tags'));
    }

    public function store(StorePostRequest $request)
    {
        $validated = $request->validated();
        $validated['slug'] = Str::slug($validated['title']);
        $validated['user_id'] = auth()->id();

        // 画像アップロード処理
        if ($request->hasFile('featured_image')) {
            $validated['featured_image'] = $request->file('featured_image')
                ->store('posts', 'public');
        }

        $post = Post::create($validated);

        // タグの関連付け
        if (isset($validated['tags'])) {
            $post->tags()->sync($validated['tags']);
        }

        return redirect()
            ->route('posts.show', $post)
            ->with('success', '投稿が作成されました。');
    }

    public function edit(Post $post)
    {
        $this->authorize('update', $post);
        $tags = Tag::all();
        return view('posts.edit', compact('post', 'tags'));
    }

    public function update(StorePostRequest $request, Post $post)
    {
        $this->authorize('update', $post);
        
        $validated = $request->validated();
        $validated['slug'] = Str::slug($validated['title']);

        // 新しい画像がアップロードされた場合
        if ($request->hasFile('featured_image')) {
            // 古い画像を削除
            if ($post->featured_image) {
                Storage::disk('public')->delete($post->featured_image);
            }
            $validated['featured_image'] = $request->file('featured_image')
                ->store('posts', 'public');
        }

        $post->update($validated);

        // タグの関連付けを更新
        if (isset($validated['tags'])) {
            $post->tags()->sync($validated['tags']);
        }

        return redirect()
            ->route('posts.show', $post)
            ->with('success', '投稿が更新されました。');
    }

    public function destroy(Post $post)
    {
        $this->authorize('delete', $post);
        
        // 画像ファイルの削除
        if ($post->featured_image) {
            Storage::disk('public')->delete($post->featured_image);
        }

        $post->delete();

        return redirect()
            ->route('posts.index')
            ->with('success', '投稿が削除されました。');
    }
}

Artisanコマンド

// カスタムコマンド生成
// php artisan make:command ProcessPosts

// app/Console/Commands/ProcessPosts.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Mail;

class ProcessPosts extends Command
{
    protected $signature = 'posts:process 
                           {--user= : 特定のユーザーのみ処理}
                           {--days=30 : 処理対象の日数}
                           {--dry-run : 実際の処理は行わず、結果のみ表示}';

    protected $description = '投稿の定期処理を実行します';

    public function handle()
    {
        $this->info('投稿処理を開始します...');

        $days = $this->option('days');
        $userId = $this->option('user');
        $dryRun = $this->option('dry-run');

        // プログレスバーの初期化
        $query = Post::where('created_at', '<=', now()->subDays($days));
        
        if ($userId) {
            $query->where('user_id', $userId);
        }

        $posts = $query->get();
        $bar = $this->output->createProgressBar($posts->count());

        $processed = 0;
        $errors = 0;

        foreach ($posts as $post) {
            try {
                if (!$dryRun) {
                    // 実際の処理
                    $this->processPost($post);
                }
                $processed++;
                
                if ($this->output->isVerbose()) {
                    $this->line("\n処理完了: {$post->title}");
                }
            } catch (\Exception $e) {
                $errors++;
                $this->error("\nエラー: {$post->title} - {$e->getMessage()}");
            }

            $bar->advance();
        }

        $bar->finish();

        // 結果の表示
        $this->newLine(2);
        $this->info("処理完了:");
        $this->table(
            ['項目', '件数'],
            [
                ['処理対象', $posts->count()],
                ['処理成功', $processed],
                ['エラー', $errors],
                ['乾燥実行', $dryRun ? 'はい' : 'いいえ']
            ]
        );

        // ユーザーに確認を求める
        if ($errors > 0 && $this->confirm('エラーの詳細を表示しますか?')) {
            $this->info('エラーログを確認してください。');
        }

        return $errors > 0 ? 1 : 0;
    }

    private function processPost(Post $post)
    {
        // 投稿の処理ロジック
        if (!$post->published && $post->created_at->diffInDays() > 30) {
            $post->delete();
            $this->warn("下書き投稿を削除: {$post->title}");
        }
    }
}

// ジョブの作成と実行
// php artisan make:job SendNewsletterJob

// app/Jobs/SendNewsletterJob.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
use App\Mail\Newsletter;
use Illuminate\Support\Facades\Mail;

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

    protected $newsletter;
    protected $users;

    public function __construct($newsletter, $users)
    {
        $this->newsletter = $newsletter;
        $this->users = $users;
    }

    public function handle()
    {
        foreach ($this->users as $user) {
            Mail::to($user)->queue(new Newsletter($this->newsletter));
        }
    }
}

// スケジュール設定
// app/Console/Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    protected $commands = [
        Commands\ProcessPosts::class,
    ];

    protected function schedule(Schedule $schedule)
    {
        // 毎日午前2時に実行
        $schedule->command('posts:process')->dailyAt('02:00');

        // 毎週月曜日に実行
        $schedule->command('posts:process --days=7')
                 ->weeklyOn(1, '03:00');

        // システムメンテナンス
        $schedule->command('model:prune')->daily();
        $schedule->command('queue:work --stop-when-empty')->everyMinute();

        // バックアップ
        $schedule->command('backup:run')->daily();
    }

    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}

// Artisanコマンドの実行
// php artisan posts:process
// php artisan posts:process --user=1 --days=7 --dry-run
// php artisan posts:process --help
// php artisan queue:work
// php artisan schedule:run
// php artisan schedule:list