Laravel
PHPで最も人気の高いフルスタックWebフレームワーク。Eloquent ORMやBladeテンプレートエンジンを搭載し、開発効率性に優れている。
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.
トピックス
スター履歴
フレームワーク
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フレームワークと比較して若干重い
- バージョン互換性: メジャーアップデートでの破壊的変更
- メモリ使用量: 大規模アプリケーションでのメモリ消費が大きい
- 過度な抽象化: シンプルなタスクでも複雑になる場合がある
主要リンク
- Laravel公式サイト
- Laravel公式ドキュメント
- Laravel GitHub リポジトリ
- Laracasts(学習リソース)
- Laravel News
- Packagist(Composer)
書き方の例
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