📌 Ne Zaman Kullanılır?
- ✅ Web uygulama, API, MVP, freelance, e-ticaret, CMS
- ⚠️ Microservices için dikkatli ol (monolith ağırlıklı)
- ❌ Real-time ağırlıklı proje (Node.js daha uygun)
Önerilen Kullanım: Laravel + MySQL/PostgreSQL + Blade/Inertia Alternatifler: Node.js (Express), Django, ASP.NET Core
Laravel Rehberi — Beginner to Production
Complete English + Turkish learning guide for Laravel developers. (Laravel gelisitiricileri için Ingilizce + Turkce tam öğrenme rehberi.)
Scope: Laravel 11/12, PHP 8.3+, MVC fundamentals, Eloquent ORM, REST API, Sanctum auth, Pest testing, Docker, CI/CD. (Kapsam: Laravel temelleri, veritabani, API geliştirme, kimlik doğrulama, test, deployment.)
Fundamentals (Temeller)
What is Laravel? (Laravel Nedir?)
Laravel is a PHP web application framework that follows the MVC (Model-View-Controller) design pattern. It provides built-in features for routing, authentication, database management, and much more.
Key benefits:
- Clean and elegant syntax (Temiz ve anlasilir sozdizimi)
- Built-in tools for authentication, queues, and caching (Hazir kimlik doğrulama, kuyruk, önbellek sistemleri)
- Follows MVC architecture for maintainable code (MVC mimarisiyle surdurulebilir kod yapisi)
- Integrated ORM (Eloquent) for database operations (Veritabani islemleri için Eloquent ORM)
Tech Stack & Versions
- PHP 8.3+, Laravel 11/12
- DB: MySQL 8 / MariaDB 10.11 / PostgreSQL 15+
- Auth: Laravel Sanctum (SPA/API tokens)
- Test: Pest + HTTP tests + factories/seeders
- Docs: OpenAPI 3.1 via L5-Swagger
- Queue: Redis + Horizon (optional)
- Cache: Redis / Memcached
- Observability: Laravel Log, Telescope (dev), Sentry (prod)
Installing Laravel (Kurulum)
Prerequisites (Gereksinimler):
- PHP 8.2 or higher
- Composer (PHP dependency manager)
- Node.js (for frontend build tools like Vite)
- MySQL or SQLite (database)
# Create a new project (Yeni proje olusturma)
composer create-project laravel/laravel my-app
cd my-app
php artisan serveNow visit: http://127.0.0.1:8000 (Artik tarayicida http://127.0.0.1:8000 adresine giderek projenin calistigini gorebilirsin.)
For API projects (API projeleri için ek kurulum)
composer require laravel/sanctum
composer require darkaonline/l5-swagger --dev
composer require predis/predis --dev
composer require pestphp/pest --dev
php artisan pest:install
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateFolder Structure (Klasor Yapisi)
| Directory | Purpose |
|---|---|
app/ | Core application logic (models, controllers, etc.) |
bootstrap/ | Starts Laravel and loads configuration |
config/ | Configuration files (database, mail, cache, etc.) |
database/ | Migrations, seeders, factories |
public/ | Publicly accessible folder (index.php, assets) |
resources/ | Views, Blade templates, and frontend assets |
routes/ | Defines all routes of your application |
storage/ | Logs, cache, uploaded files |
tests/ | Automated tests |
.env | Environment configuration |
Each folder has a specific responsibility. Learning what goes where helps structure your project efficiently. (Her klasorun belirli bir amaci vardir. Hangisinin ne ise yaradigini bilmek projeni duzenli kurmani sağlar.)
API project recommended layout (API proje yapisi):
app/
Actions/ # Use-cases (is mantigi; kucuk siniflar)
DTOs/ # Immutable data transfer objects
Http/
Controllers/Api/V1/
Middleware/
Requests/
Resources/ # API Transformers
Models/
Policies/
Services/ # Integrations (Payment, Storage, Mail, etc.)
Support/ # Helpers, TraitsMVC Architecture (MVC Mimarisi)
Model (M) — Represents your data and logic. Each database table usually has a model.
class Post extends Model {
protected $fillable = ['title', 'content'];
}View (V) — Responsible for displaying data to users using Blade templates.
<h1>{{ $post->title }}</h1>
<p>{{ $post->content }}</p>Controller (C) — Handles requests, calls models, and returns views.
class PostController extends Controller {
public function index() {
$posts = Post::all();
return view('posts.index', compact('posts'));
}
}MVC separates logic, design, and data for cleaner code. (MVC yapisi; veriyi, gorunumu ve mantigi ayirarak kodu daha duzenli hale getirir.)
Configuration & .env
The .env file stores sensitive information like database credentials, app name, and API keys.
APP_NAME="MyApp"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my_app
DB_USERNAME=root
DB_PASSWORD=
CACHE_STORE=redis
QUEUE_CONNECTION=redis
SANCTUM_STATEFUL_DOMAINS=localhost:3000,localhost:5173
SESSION_DOMAIN=localhostTip: Never share .env publicly! (Asla .env dosyasini paylasma -- gizlidir.)
php artisan config:clear
php artisan config:cacheArtisan CLI (Komut Satiri Araci)
Laravel's command-line tool is called Artisan. It helps automate repetitive tasks.
php artisan list # Show all commands
php artisan make:model Post -m # Create model and migration
php artisan migrate # Run migrations
php artisan serve # Start development server
php artisan make:controller PostController --resource --model=Post
php artisan make:request PostRequest
php artisan make:middleware EnsureAdminArtisan makes development faster and more consistent. (Artisan, gelistirmeyi hizlandirir ve projede tutarlilik sağlar.)
Routing (Rotalar)
Routes define how users access your application.
Basic Routes
// routes/web.php
Route::get('/', function () {
return view('welcome');
});
Route::get('/posts', [PostController::class, 'index']);Route Parameters
Route::get('/posts/{id}', [PostController::class, 'show']);
Route::get('/posts/{id}', [PostController::class, 'show'])->where('id', '[0-9]+');Named Routes
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
return redirect()->route('dashboard');Route Groups & Middleware
Route::middleware('admin')->group(function() {
Route::get('/dashboard', [AdminController::class, 'index']);
});API Routing & Versioning
// routes/api.php
use App\Http\Controllers\Api\V1\ProjectController;
Route::prefix('v1')->name('api.v1.')->group(function () {
Route::post('auth/login', [AuthController::class, 'login'])->name('login');
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('projects', ProjectController::class);
Route::get('me', [AuthController::class, 'me']);
Route::post('auth/logout', [AuthController::class, 'logout']);
});
});Routes connect URLs to logic. API routes use resource-first REST with plural nouns (
/api/v1/users). (Rotalar, kullanici istegini uygulama mantigina yonlendirir.)
Controllers (Denetleyiciler)
Controllers handle logic between routes and views/responses.
Web Controller
class PostController extends Controller
{
public function index() {
$posts = Post::latest()->get();
return view('posts.index', compact('posts'));
}
public function show(Post $post) {
return view('posts.show', compact('post'));
}
}API Controller (Action Pattern)
For APIs, keep controllers thin. Move business logic into Action classes.
// app/DTOs/ProjectData.php
readonly class ProjectData {
public function __construct(
public string $name,
public ?string $description = null,
) {}
public static function fromRequest(StoreProjectRequest $r): self {
return new self($r->name, $r->validated('description'));
}
}
// app/Actions/Project/CreateProject.php
class CreateProject {
public function __invoke(ProjectData $data): Project {
return Project::create((array) $data);
}
}
// app/Http/Controllers/Api/V1/ProjectController.php
class ProjectController extends Controller
{
public function index(IndexProjectRequest $r)
{
$q = Project::query();
return ProjectResource::collection($q->paginate($r->get('per_page', 15)));
}
public function store(StoreProjectRequest $r, CreateProject $action)
{
$project = $action(ProjectData::fromRequest($r));
return new ProjectResource($project);
}
public function show(Project $project) { return new ProjectResource($project); }
public function update(UpdateProjectRequest $r, Project $project, UpdateProject $action)
{
$project = $action($project, ProjectData::fromRequest($r));
return new ProjectResource($project);
}
public function destroy(Project $project, DeleteProject $action)
{
$action($project);
return response()->json(['status'=>'success'], 204);
}
}Small controllers, thin models, fat actions -- improves testability. (Controller'lari ince tut, mantigi Action/Service katmanina tasi.)
Controller - Service - Repository - Model Katmanli Mimari
Büyük projelerde is mantigi dogrudan controller icinde yazilmamalidir. Bunun yerine katmanli mimari (Layered Architecture) kullanilir. Her katmanin tek bir sorumlulugu vardir:
| Katman | Sorumluluk |
|---|---|
| Controller | HTTP istegini alir, response dondurur |
| Service | Is mantigi (business logic) burada yashar |
| Repository | Veritabani sorguları burada soyutlanir |
| Model | Tablo tanimlari, iliskiler, cast'ler |
Asagida bir Product (Urun) CRUD ornegi ile tüm katmanlari goreceksiniz.
Model
// app/Models/Product.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'slug',
'description',
'price',
'stock',
'is_active',
'category_id',
];
protected $casts = [
'price' => 'decimal:2',
'is_active' => 'boolean',
'stock' => 'integer',
];
// -- Relationships --
public function category()
{
return $this->belongsTo(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
public function images()
{
return $this->hasMany(ProductImage::class);
}
// -- Scopes --
public function scopeActive($query)
{
return $query->where('is_active', true);
}
public function scopeInStock($query)
{
return $query->where('stock', '>', 0);
}
}Repository Interface & Implementation
// app/Repositories/Contracts/ProductRepositoryInterface.php
namespace App\Repositories\Contracts;
use App\Models\Product;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
interface ProductRepositoryInterface
{
public function paginate(array $filters = [], int $perPage = 15): LengthAwarePaginator;
public function findById(int $id): ?Product;
public function findBySlug(string $slug): ?Product;
public function create(array $data): Product;
public function update(Product $product, array $data): Product;
public function delete(Product $product): bool;
}// app/Repositories/Eloquent/ProductRepository.php
namespace App\Repositories\Eloquent;
use App\Models\Product;
use App\Repositories\Contracts\ProductRepositoryInterface;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class ProductRepository implements ProductRepositoryInterface
{
public function paginate(array $filters = [], int $perPage = 15): LengthAwarePaginator
{
$query = Product::query()->with(['category', 'tags']);
if (! empty($filters['category_id'])) {
$query->where('category_id', $filters['category_id']);
}
if (isset($filters['is_active'])) {
$query->where('is_active', $filters['is_active']);
}
if (! empty($filters['search'])) {
$query->where(function ($q) use ($filters) {
$q->where('name', 'like', "%{$filters['search']}%")
->orWhere('description', 'like', "%{$filters['search']}%");
});
}
if (! empty($filters['min_price'])) {
$query->where('price', '>=', $filters['min_price']);
}
if (! empty($filters['max_price'])) {
$query->where('price', '<=', $filters['max_price']);
}
$sortField = $filters['sort'] ?? 'created_at';
$sortDirection = $filters['direction'] ?? 'desc';
$query->orderBy($sortField, $sortDirection);
return $query->paginate($perPage);
}
public function findById(int $id): ?Product
{
return Product::with(['category', 'tags', 'images'])->find($id);
}
public function findBySlug(string $slug): ?Product
{
return Product::with(['category', 'tags', 'images'])
->where('slug', $slug)
->first();
}
public function create(array $data): Product
{
return Product::create($data);
}
public function update(Product $product, array $data): Product
{
$product->update($data);
return $product->fresh();
}
public function delete(Product $product): bool
{
return $product->delete();
}
}Service Provider ile Binding
// app/Providers/RepositoryServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Repositories\Contracts\ProductRepositoryInterface;
use App\Repositories\Eloquent\ProductRepository;
class RepositoryServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
ProductRepositoryInterface::class,
ProductRepository::class
);
}
}config/app.php icindeki providers dizisine ekleyin:
App\Providers\RepositoryServiceProvider::class,Service Katmani
// app/Services/ProductService.php
namespace App\Services;
use App\Models\Product;
use App\Repositories\Contracts\ProductRepositoryInterface;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
class ProductService
{
public function __construct(
protected ProductRepositoryInterface $productRepo
) {}
public function list(array $filters = [], int $perPage = 15): LengthAwarePaginator
{
$cacheKey = 'products.list:' . md5(serialize($filters) . $perPage);
return Cache::remember($cacheKey, 60, function () use ($filters, $perPage) {
return $this->productRepo->paginate($filters, $perPage);
});
}
public function getById(int $id): ?Product
{
return Cache::remember("products.{$id}", 120, function () use ($id) {
return $this->productRepo->findById($id);
});
}
public function create(array $data): Product
{
return DB::transaction(function () use ($data) {
$data['slug'] = Str::slug($data['name']);
$product = $this->productRepo->create($data);
// Tag iliskisi varsa bagla
if (! empty($data['tag_ids'])) {
$product->tags()->sync($data['tag_ids']);
}
$this->clearCache();
return $product->load(['category', 'tags']);
});
}
public function update(Product $product, array $data): Product
{
return DB::transaction(function () use ($product, $data) {
if (isset($data['name'])) {
$data['slug'] = Str::slug($data['name']);
}
$product = $this->productRepo->update($product, $data);
if (array_key_exists('tag_ids', $data)) {
$product->tags()->sync($data['tag_ids'] ?? []);
}
$this->clearCache();
Cache::forget("products.{$product->id}");
return $product->load(['category', 'tags']);
});
}
public function delete(Product $product): bool
{
$result = $this->productRepo->delete($product);
$this->clearCache();
Cache::forget("products.{$product->id}");
return $result;
}
protected function clearCache(): void
{
// Liste cache'lerini temizle
// Basit yaklasim: tag-based cache kullanilabilir
Cache::flush(); // Prod icin daha ince cache invalidation onerilir
}
}Form Request (Validation)
// app/Http/Requests/StoreProductRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreProductRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Policy ile kontrol edilebilir
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string|max:5000',
'price' => 'required|numeric|min:0|max:999999.99',
'stock' => 'required|integer|min:0',
'is_active' => 'boolean',
'category_id' => 'required|exists:categories,id',
'tag_ids' => 'nullable|array',
'tag_ids.*' => 'exists:tags,id',
];
}
public function messages(): array
{
return [
'name.required' => 'Urun adi zorunludur.',
'price.required' => 'Fiyat alani zorunludur.',
'category_id.exists' => 'Gecersiz kategori.',
];
}
}// app/Http/Requests/UpdateProductRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateProductRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => 'sometimes|string|max:255',
'description' => 'nullable|string|max:5000',
'price' => 'sometimes|numeric|min:0|max:999999.99',
'stock' => 'sometimes|integer|min:0',
'is_active' => 'boolean',
'category_id' => 'sometimes|exists:categories,id',
'tag_ids' => 'nullable|array',
'tag_ids.*' => 'exists:tags,id',
];
}
}API Resource (Transformer)
// app/Http/Resources/ProductResource.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'price' => $this->price,
'stock' => $this->stock,
'is_active' => $this->is_active,
'category' => new CategoryResource($this->whenLoaded('category')),
'tags' => TagResource::collection($this->whenLoaded('tags')),
'images' => ProductImageResource::collection($this->whenLoaded('images')),
'created_at' => $this->created_at?->toJSON(),
'updated_at' => $this->updated_at?->toJSON(),
];
}
}Controller
// app/Http/Controllers/Api/V1/ProductController.php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreProductRequest;
use App\Http\Requests\UpdateProductRequest;
use App\Http\Resources\ProductResource;
use App\Models\Product;
use App\Services\ProductService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class ProductController extends Controller
{
public function __construct(
protected ProductService $productService
) {}
/**
* Urun listesi (filtreleme, siralama, sayfalama)
*/
public function index(Request $request): AnonymousResourceCollection
{
$filters = $request->only([
'category_id', 'is_active', 'search',
'min_price', 'max_price', 'sort', 'direction',
]);
$products = $this->productService->list(
$filters,
$request->integer('per_page', 15)
);
return ProductResource::collection($products);
}
/**
* Urun detayi
*/
public function show(int $id): ProductResource|JsonResponse
{
$product = $this->productService->getById($id);
if (! $product) {
return response()->json([
'status' => 'error',
'error' => [
'code' => 'NOT_FOUND',
'title' => 'Urun bulunamadi',
],
], 404);
}
return new ProductResource($product);
}
/**
* Yeni urun olustur
*/
public function store(StoreProductRequest $request): JsonResponse
{
$product = $this->productService->create($request->validated());
return (new ProductResource($product))
->response()
->setStatusCode(201);
}
/**
* Urun guncelle
*/
public function update(UpdateProductRequest $request, Product $product): ProductResource
{
$product = $this->productService->update($product, $request->validated());
return new ProductResource($product);
}
/**
* Urun sil (soft delete)
*/
public function destroy(Product $product): JsonResponse
{
$this->productService->delete($product);
return response()->json(['status' => 'success'], 204);
}
}Route Tanimi
// routes/api.php
Route::prefix('v1')->name('api.v1.')->group(function () {
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('products', \App\Http\Controllers\Api\V1\ProductController::class);
});
});Bu katmanli yaklasim sayesinde her sınıf tek bir sorumluluga sahip olur, testler kolaylasir ve proje buyudukce yönetimi basit kalir. (Katmanli mimari = tek sorumluluk + kolay test + olceklenebilirlik.)
Validation & Middleware
Validation (Doğrulama)
Validation ensures user input is correct before processing.
Quick validation:
$request->validate([
'title' => 'required|min:3',
'content' => 'required'
]);Form Request class:
class StoreProjectRequest extends FormRequest {
public function authorize(): bool {
return $this->user()?->can('create', Project::class) ?? false;
}
public function rules(): array {
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string'
];
}
}Use in controller:
public function store(StoreProjectRequest $request) {
Post::create($request->validated());
return redirect()->back();
}Validation protects data integrity. (Doğrulama, verinin dogrulugunu korur ve hatali girisi onler.)
Middleware (Ara Katman)
Middleware are filters that run before or after a request.
class EnsureAdmin {
public function handle($request, Closure $next) {
if (!auth()->user() || !auth()->user()->is_admin) {
return redirect('/');
}
return $next($request);
}
}Register middleware:
protected $routeMiddleware = [
'admin' => \App\Http\Middleware\EnsureAdmin::class,
];Middleware keeps access control simple and reusable. (Middleware, erisim kontrolunu basit ve moduler hale getirir.)
Request Lifecycle (Istek Dongusu)
- Laravel receives request via
public/index.php - Routes decide which controller handles it
- Middleware filters it (auth, CSRF, etc.)
- Controller processes and returns a response
- Response is sent to the client
Blade Templates (Sablonlar)
Laravel uses Blade, a lightweight templating engine.
<!-- resources/views/posts/index.blade.php -->
@extends('layouts.app')
@section('content')
<h1>Posts</h1>
@foreach($posts as $post)
<a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a><br>
@endforeach
@endsectionBlade Directives
@if($user)
<p>Welcome, {{ $user->name }}</p>
@else
<p>Please log in.</p>
@endif
@foreach($posts as $post)
<li>{{ $post->title }}</li>
@endforeachSession & Flash Messages (Oturum ve Mesajlar)
session(['key' => 'value']);
echo session('key');
return redirect()->back()->with('success', 'Post created successfully!');In Blade:
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endifBlade simplifies view logic with minimal syntax. (Blade, gorunumleri yazmayi kolaylastirir ve temiz tutar.)
Eloquent ORM & Database
Migrations (Veritabani Tasima Sistemi)
Migrations are like version control for your database.
php artisan make:migration create_posts_tableSchema::create('projects', function (Blueprint $t) {
$t->id();
$t->string('name');
$t->text('description')->nullable();
$t->timestamps();
$t->softDeletes();
});php artisan migrateMigrations keep database structure synchronized between developers. (Migration'lar, ekipteki herkesin ayni veritabani yapisini kullanmasini sağlar.)
Seeders & Factories (Test Verileri)
// database/seeders/DatabaseSeeder.php
public function run(): void
{
\App\Models\User::factory()->create([
'name' => 'Admin',
'email' => 'admin@example.com',
]);
\App\Models\Project::factory(10)->create();
}php artisan db:seed
php artisan tinker
Post::factory()->count(10)->create();Seeders and factories make it easy to test your app with sample data. (Seeder ve Factory yapilari, test için hızlı veri uretmeni sağlar.)
Basic Eloquent Usage
Eloquent allows you to interact with the database using models instead of SQL.
// Retrieving
$posts = Post::all();
$post = Post::find(1);
$recent = Post::where('status', 'published')->orderBy('created_at', 'desc')->take(5)->get();
// Creating
Post::create(['title' => 'Eloquent Power', 'content' => 'Simple database operations.']);
// Updating
$post = Post::find(1);
$post->title = 'Updated Title';
$post->save();
// Deleting
Post::destroy(5);Eloquent Model Conventions
class Project extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['name', 'description'];
public function tasks() { return $this->hasMany(Task::class); }
}Conventions: use fillable for mass-assignment safety; soft deletes are recommended for production. (fillable kullanin, mass-assignment guvenligi; soft deletes prod için faydalidir.)
Relationships (Iliskiler)
One-to-One
class User extends Model {
public function profile() {
return $this->hasOne(Profile::class);
}
}
$user = User::find(1);
$profile = $user->profile;One-to-Many
class User extends Model {
public function posts() {
return $this->hasMany(Post::class);
}
}
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}Many-to-Many
class Post extends Model {
public function tags() {
return $this->belongsToMany(Tag::class);
}
}
// Pivot table name must follow convention: post_tag
$post->tags()->attach([1, 2, 3]);
$post->tags()->sync([2, 3]);Relationships model real-world connections between entities. (Iliskiler, gercek dunyadaki varliklar arasindaki baglantilari temsil eder.)
Advanced Eloquent
Query Scopes (Sorgu Kapsamlari)
Local Scope:
class Post extends Model {
public function scopePublished($query) {
return $query->where('status', 'published');
}
}
$posts = Post::published()->get();Global Scope:
class PublishedScope implements Scope {
public function apply(Builder $builder, Model $model) {
$builder->where('status', 'published');
}
}
protected static function booted() {
static::addGlobalScope(new PublishedScope);
}Accessors & Mutators (Erisimciler ve Donusturuculer)
// Accessor -- modifies output
public function getTitleAttribute($value) {
return ucfirst($value);
}
// Mutator -- modifies input
public function setTitleAttribute($value) {
$this->attributes['title'] = strtolower($value);
}Casting & Attributes (Veri Donusturme)
protected $casts = [
'is_published' => 'boolean',
'meta' => 'array',
'published_at' => 'datetime',
];Soft Deletes (Yumusak Silme)
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model {
use SoftDeletes;
}
Post::find(1)->delete(); // Soft delete
Post::withTrashed()->get(); // Include deleted
Post::onlyTrashed()->restore(); // RestoreEvents & Observers (Olaylar ve Gozlemciler)
Model Events:
class Post extends Model {
protected static function booted() {
static::creating(function ($post) {
$post->slug = Str::slug($post->title);
});
}
}Observers:
php artisan make:observer PostObserver --model=Postclass PostObserver {
public function created(Post $post) {
Log::info('Post created: ' . $post->id);
}
}
// Register in AppServiceProvider
Post::observe(PostObserver::class);Pagination (Sayfalama)
$posts = Post::paginate(10);In Blade:
{{ $posts->links() }}Query Builder
When you need flexibility beyond Eloquent:
$users = DB::table('users')
->where('active', 1)
->whereBetween('age', [18, 30])
->orderBy('name')
->get();Performance Optimization (Performans Optimizasyonu)
Eager Loading -- avoids N+1 queries:
$posts = Post::with('user', 'tags')->get();Chunking -- process large datasets:
Post::chunk(100, function($posts) {
foreach ($posts as $post) {
// process
}
});Caching:
$posts = Cache::remember('posts.all', 3600, fn() => Post::all());
// For API index endpoints
$projects = Cache::remember("projects.index:{md5(request()->fullUrl())}", 60, fn() =>
Project::query()->withCount('tasks')->paginate(20)
);Database Indexing:
$table->index('email');These techniques significantly reduce query load. (Bu teknikler sorgu yukunu büyük olcude azaltir.)
Architecture & Design Patterns
Service Container (Servis Kapsayici)
The Service Container manages class dependencies and performs Dependency Injection automatically.
// Without DI
class ReportController extends Controller {
public function index() {
$service = new ReportService();
return $service->generate();
}
}
// With DI -- Laravel auto-injects
class ReportController extends Controller {
public function index(ReportService $service) {
return $service->generate();
}
}Binding in the Container
use App\Services\PaymentService;
$this->app->bind(PaymentService::class, function ($app) {
return new PaymentService('stripe');
});Service Providers (Servis Saglayicilar)
Service Providers are the entry points where Laravel bootstraps components.
php artisan make:provider ReportServiceProviderclass ReportServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton(ReportService::class, function ($app) {
return new ReportService();
});
}
public function boot() {
// Code that runs after all services are registered
}
}Register the provider in config/app.php under providers.
Repository Pattern (Depo Deseni)
Abstracts database logic from controllers.
interface PostRepositoryInterface {
public function getAll();
}
class PostRepository implements PostRepositoryInterface {
public function getAll() {
return Post::all();
}
}
// Binding in Provider
$this->app->bind(PostRepositoryInterface::class, PostRepository::class);
// Controller Usage
class PostController extends Controller {
public function __construct(PostRepositoryInterface $repo) {
$this->repo = $repo;
}
public function index() {
return $this->repo->getAll();
}
}Facades (Yuzeyler)
use Illuminate\Support\Facades\Cache;
Cache::put('key', 'value', 3600);
echo Cache::get('key');Custom Helpers (Ozel Yardimci Fonksiyonlar)
Create app/helpers.php:
function formatDate($date) {
return \Carbon\Carbon::parse($date)->format('d/m/Y');
}Include in composer.json:
"autoload": {
"files": ["app/helpers.php"]
}Then: composer dump-autoload
Event-Driven Architecture (Olay Tabanli Mimari)
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegisteredclass UserRegistered {
public $user;
public function __construct(User $user) {
$this->user = $user;
}
}
class SendWelcomeEmail {
public function handle(UserRegistered $event) {
Mail::to($event->user->email)->send(new WelcomeMail($event->user));
}
}Register in EventServiceProvider.
Design Patterns Summary
| Pattern | Purpose | Example |
|---|---|---|
| Singleton | One shared instance | app()->singleton() |
| Factory | Creates objects dynamically | Model Factories |
| Repository | Separates data layer | PostRepository |
| Strategy | Switch between algorithms | PaymentService (Stripe/PayPal) |
| Observer | Listens to model events | PostObserver |
Service Architecture Best Practices
- Keep controllers thin, move logic into services. (Controller'lari sade tut, mantigi servislere tasi.)
- Use interfaces for flexibility. (Esneklik için interface kullan.)
- Group related services in modules. (Ilgili servisleri moduller halinde topla.)
- Avoid Facades in testing; use dependency injection. (Testlerde Facade yerine DI tercih et.)
- Cache expensive operations. (Maliyetli islemleri onbellege al.)
Authentication (Kimlik Doğrulama)
Breeze (Web Authentication)
For traditional web apps with login/register:
composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev
php artisan migrateThis scaffolds basic auth (login, register, forgot password) using Blade or React.
Sanctum (API Authentication)
For SPA or mobile API authentication:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateToken issue and auth endpoints:
// app/Http/Controllers/Api/V1/AuthController.php
class AuthController extends Controller
{
public function login(Request $r)
{
$r->validate(['email'=>'required|email', 'password'=>'required']);
$user = User::whereEmail($r->email)->first();
if (! $user || ! Hash::check($r->password, $user->password)) {
return response()->json([
'status'=>'error',
'error'=>['code'=>'INVALID_CREDENTIALS', 'title'=>'Invalid credentials']
], 422);
}
$abilities = ['projects:create', 'projects:update', 'projects:delete'];
$token = $user->createToken('api', $abilities);
return response()->json(['status'=>'success', 'data'=>['token'=>$token->plainTextToken]]);
}
public function me(Request $r) { return new UserResource($r->user()); }
public function logout(Request $r) {
$r->user()->currentAccessToken()->delete();
return response()->json(['status'=>'success']);
}
}Use with cURL:
curl -X POST http://localhost/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"password"}'Policies (Authorization)
class ProjectPolicy {
public function viewAny(User $u): bool { return true; }
public function view(User $u, Project $p): bool { return true; }
public function create(User $u): bool { return $u->tokenCan('projects:create'); }
public function update(User $u, Project $p): bool { return $u->id === $p->user_id || $u->tokenCan('projects:update'); }
public function delete(User $u, Project $p): bool { return $u->tokenCan('projects:delete'); }
}Register in AuthServiceProvider:
protected $policies = [ Project::class => ProjectPolicy::class ];Sanctum: Token abilities enable fine-grained authorization. (Sanctum: Token yetenekleri/abilities ile ince yetkilendirme.)
API Development
Design Principles
- Resource-first REST, nouns plural (
/api/v1/users), predictable verbs (GET/POST/PATCH/DELETE). (Kaynak merkezli REST; isimler cogul.) - Stable response envelope:
{ "status": "success|error", "data": ..., "meta": ... }. (Sabit cevap zarfi, hata/success tek bicim.) - Idempotency for creates/updates where applicable via
Idempotency-Key. - Validation at edges; Policies/Gates for authorization.
- Small controllers, thin models, fat actions (use Action/Service layer).
- Deterministic errors (machine parseable
code,title,detail). - 12-factor config; prod parity in Docker; repeatable from
make up.
Response Shape & Error Handling
Success envelope:
{ "status":"success", "data": { "id": 1, "name": "Alpha" }, "meta": { "request_id":"..." } }Error envelope:
{ "status":"error", "error": { "code":"VALIDATION_FAILED", "title":"Validation failed", "detail":"name is required", "source":{"field":"name"} } }Global handler:
// app/Exceptions/Handler.php (render method)
if ($e instanceof ValidationException) {
return response()->json([
'status'=>'error',
'error'=>[
'code'=>'VALIDATION_FAILED',
'title'=>'Validation failed',
'detail'=>$e->getMessage(),
'meta'=>$e->errors(),
]
], 422);
}API Resources (Transformers)
class ProjectResource extends JsonResource
{
public function toArray($request): array
{
return [
'id'=>$this->id,
'name'=>$this->name,
'description'=>$this->description,
'created_at'=>$this->created_at?->toJSON(),
'updated_at'=>$this->updated_at?->toJSON(),
];
}
}API Resources & Pagination (Detayli)
API Resource siniflari model verilerini istemciye sunmadan once donusturur. Hassas alanlari gizleyebilir, iliskileri koşullu yukleyebilir ve tutarli bir çıktı formati saglayabilirsiniz.
Collection Resource:
// app/Http/Resources/ProductCollection.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ProductCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'meta' => [
'total' => $this->total(),
'per_page' => $this->perPage(),
'current_page' => $this->currentPage(),
'last_page' => $this->lastPage(),
],
];
}
}Kosullu (conditional) alanlar:
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->when($request->user()?->is_admin, $this->email),
'posts_count' => $this->whenCounted('posts'),
'profile' => new ProfileResource($this->whenLoaded('profile')),
'secret_key' => $this->when($this->is_owner, $this->secret_key),
];
}Pagination kullanimi:
// Controller
public function index(Request $request)
{
$perPage = min($request->integer('per_page', 15), 100); // max 100
$products = Product::with('category')
->active()
->paginate($perPage);
return ProductResource::collection($products);
}Cursor Pagination (büyük veri setleri için):
$products = Product::orderBy('id')->cursorPaginate(20);
return ProductResource::collection($products);
cursorPaginateOFFSET kullanmaz, bu nedenle milyonlarca satirda bile hizlidir. Ancak toplam sayfa sayisi (last_page) bilgisi dondurmez.
Filtering / Sorting / Pagination / Includes
Query params convention:
filter[name]=alphasort=-created_at,name(minus = DESC)page=1&per_page=20include=tasks,user
Simple Filter class:
class QueryFilter {
public function __construct(protected Builder $query) {}
public function apply(array $filters): Builder {
foreach ($filters as $key=>$value) {
if ($value === null || $value === '') continue;
$method = 'filter'.Str::studly($key);
if (method_exists($this, $method)) { $this->{$method}($value); }
}
return $this->query;
}
}Usage in controller:
$filter = new ProjectFilter(Project::query());
$filter->apply($request->get('filter', []));
if ($sort = $request->string('sort')->toString()) {
foreach (explode(',', $sort) as $item) {
$direction = str_starts_with($item, '-') ? 'desc' : 'asc';
$col = ltrim($item, '-');
$q->orderBy($col, $direction);
}
}
return ProjectResource::collection($q->paginate($perPage));File Uploads & Images
Accept multipart/form-data. Validate image mime, max. Store using Storage::disk('public'), return signed URLs if private. (Uretimde S3/Wasabi/MinIO kullanimi tavsiye.)
$request->validate([ 'logo'=>'nullable|image|mimes:jpeg,png,webp|max:4096' ]);
$path = $request->file('logo')?->store('logos', 'public');
return response()->json(['status'=>'success', 'data'=>['logo_url'=>Storage::disk('public')->url($path)]]);Events, Queues & Notifications
Dispatch domain events on create/update. Offload emails/webhooks to queue. (Redis + Horizon ile job'lari izleyin.)
ProjectCreated::dispatch($project);Localization (i18n)
Validation messages per locale. For multi-language data, use projects + project_translations tables.
Language files:
resources/lang/en/messages.php
resources/lang/tr/messages.phpreturn ['welcome' => 'Welcome to our site!'];In Blade: {{ __('messages.welcome') }}
Logging, Observability & Rate Limits
apimiddleware already hasthrottle:api. Tune inRouteServiceProvider.- Add request id: middleware that adds
X-Request-Id. - Send exceptions to Sentry in production.
Route::middleware(['throttle:60,1'])->group(function(){ /* ... */ });API Docs (OpenAPI/Swagger)
Annotate controllers/resources with OpenAPI PHPDoc, then:
php artisan l5-swagger:generateDocs UI opens at /api/documentation (default). (Uretimde dokuman erisimini yetki ile kisitlayin.)
HTTP Status Code Mapping
| Code | Meaning |
|---|---|
| 200 | OK (GET) |
| 201 | Created (POST create) |
| 204 | No Content (DELETE/empty success) |
| 400 | Bad Request (client syntax) |
| 401 | Unauthorized (auth missing/invalid) |
| 403 | Forbidden (authz denied) |
| 404 | Not Found |
| 422 | Validation Failed |
| 429 | Too Many Requests |
| 500 | Server Error |
cURL Examples
# List
curl -H "Authorization: Bearer <token>" http://localhost/api/v1/projects
# Create
curl -X POST http://localhost/api/v1/projects \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"New Project"}'API Coding Style
- Controller <= ~80 LOC.
- Action names:
CreateX,UpdateX,DeleteX. - DTOs are readonly.
- Resources for every externalized model.
- Use
StoreXRequest/UpdateXRequestper resource.
Queue & Job Sistemi
Laravel'in kuyruk (queue) sistemi, uzun suren islemleri arka plana atmanizi sağlar. E-posta gonderimi, dosya işleme, bildirim gonderme gibi islemler kullaniciyi bekletmeden yapılabilir.
Yapılandırma
# .env
QUEUE_CONNECTION=redisconfig/queue.php dosyasindan farkli kuyruk baglantilari ayarlanabilir: sync, database, redis, sqs.
Job Oluşturma
php artisan make:job ProcessOrderJob// app/Jobs/ProcessOrderJob.php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessOrderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3; // Maksimum deneme sayisi
public int $timeout = 120; // Saniye cinsinden zaman asimi
public int $backoff = 60; // Basarisizliktan sonra bekleme suresi (sn)
public function __construct(
public Order $order
) {}
public function handle(): void
{
// Siparis islemlerini burada yap
$this->order->update(['status' => 'processing']);
// Odeme islemi, stok dusme, fatura olusturma vb.
// ...
$this->order->update(['status' => 'completed']);
}
/**
* Job basarisiz olursa cagirilir
*/
public function failed(\Throwable $exception): void
{
// Hata durumunda bildirim gonder, log yaz vb.
logger()->error('Order processing failed', [
'order_id' => $this->order->id,
'error' => $exception->getMessage(),
]);
}
}Job Dispatch Etme
use App\Jobs\ProcessOrderJob;
// Aninda kuyruğa ekle
ProcessOrderJob::dispatch($order);
// Gecikmeli dispatch (5 dakika sonra calissin)
ProcessOrderJob::dispatch($order)->delay(now()->addMinutes(5));
// Belirli bir kuyruga gonder
ProcessOrderJob::dispatch($order)->onQueue('orders');
// Zincirli job'lar (sirasıyla calisir)
use Illuminate\Support\Facades\Bus;
Bus::chain([
new ProcessOrderJob($order),
new SendInvoiceJob($order),
new NotifyCustomerJob($order),
])->dispatch();
// Batch (paralel calistir, hepsini izle)
Bus::batch([
new ImportCsvChunkJob($chunk1),
new ImportCsvChunkJob($chunk2),
new ImportCsvChunkJob($chunk3),
])->then(function ($batch) {
// Tumu basarili
})->catch(function ($batch, $e) {
// Herhangi biri basarisiz
})->finally(function ($batch) {
// Tumu bitti (basarili veya basarisiz)
})->dispatch();Queue Worker Calistirma
# Temel kullanim
php artisan queue:work
# Belirli kuyruk
php artisan queue:work --queue=orders,default
# Tek seferlik islem
php artisan queue:work --once
# Deneme sayisi ve zaman asimi
php artisan queue:work --tries=3 --timeout=90
# Bos kuyrukta bekleme suresi
php artisan queue:work --sleep=3Basarisiz Job'lar (Failed Jobs)
# Basarisiz job tablosu olustur
php artisan queue:failed-table
php artisan migrate
# Basarisiz job'lari listele
php artisan queue:failed
# Tekrar dene
php artisan queue:retry <job-id>
php artisan queue:retry all
# Basarisiz job'lari temizle
php artisan queue:flushLaravel Horizon (Redis Queue Dashboard)
Horizon, Redis tabanli kuyruk islemlerini izlemek ve yonetmek için gorsel bir dashboard sağlar.
composer require laravel/horizon
php artisan horizon:install
php artisan migrate# Horizon'u baslat
php artisan horizon
# Dashboard erisim: /horizonYapılandırma (config/horizon.php):
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default', 'orders', 'notifications'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'tries' => 3,
'timeout' => 90,
],
],
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'simple',
'processes' => 3,
'tries' => 3,
],
],
],Horizon erisim kisitlamasi (production):
// app/Providers/HorizonServiceProvider.php
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return in_array($user->email, [
'admin@example.com',
]);
});
}Kuyruk sistemi uygulamanizin performansini ciddi olcude arttirir. Uzun suren islemleri arka plana atarak kullanici deneyimini iyilestirin.
Event & Listener Sistemi
Event-Listener deseni, uygulama icerisindeki olaylari dinleyerek belirli aksiyonlari tetiklemek için kullanilir. Kodunuzu gevshek bagli (loosely coupled) tutar.
Event Oluşturma
php artisan make:event OrderPlaced// app/Events/OrderPlaced.php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderPlaced
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public Order $order
) {}
}Listener Oluşturma
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
php artisan make:listener UpdateInventory --event=OrderPlaced
php artisan make:listener NotifyAdminAboutOrder --event=OrderPlaced// app/Listeners/SendOrderConfirmation.php
namespace App\Listeners;
use App\Events\OrderPlaced;
use App\Mail\OrderConfirmationMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;
class SendOrderConfirmation implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
Mail::to($event->order->user->email)
->send(new OrderConfirmationMail($event->order));
}
}// app/Listeners/UpdateInventory.php
namespace App\Listeners;
use App\Events\OrderPlaced;
class UpdateInventory
{
public function handle(OrderPlaced $event): void
{
foreach ($event->order->items as $item) {
$item->product->decrement('stock', $item->quantity);
}
}
}Event-Listener Kayit (Registration)
// app/Providers/EventServiceProvider.php
protected $listen = [
\App\Events\OrderPlaced::class => [
\App\Listeners\SendOrderConfirmation::class,
\App\Listeners\UpdateInventory::class,
\App\Listeners\NotifyAdminAboutOrder::class,
],
\App\Events\UserRegistered::class => [
\App\Listeners\SendWelcomeEmail::class,
\App\Listeners\CreateDefaultSettings::class,
],
];Otomatik kesfetme (auto-discovery): Laravel 11+ surumlerinde listener'lar otomatik olarak kesfedilir. EventServiceProvider icinde shouldDiscoverEvents() metodunu override edin:
public function shouldDiscoverEvents(): bool
{
return true;
}Event Dispatch Etme
use App\Events\OrderPlaced;
// Controller veya Service icerisinde
OrderPlaced::dispatch($order);
// veya event helper ile
event(new OrderPlaced($order));Event Subscriber
Bir sınıf birden fazla event'i dinleyebilir:
// app/Listeners/OrderEventSubscriber.php
namespace App\Listeners;
use App\Events\OrderPlaced;
use App\Events\OrderShipped;
use App\Events\OrderCancelled;
use Illuminate\Events\Dispatcher;
class OrderEventSubscriber
{
public function handleOrderPlaced(OrderPlaced $event): void
{
logger()->info('Siparis olusturuldu', ['order_id' => $event->order->id]);
}
public function handleOrderShipped(OrderShipped $event): void
{
logger()->info('Siparis kargoye verildi', ['order_id' => $event->order->id]);
}
public function handleOrderCancelled(OrderCancelled $event): void
{
// Stok iadesi, iade islemi vb.
}
public function subscribe(Dispatcher $events): array
{
return [
OrderPlaced::class => 'handleOrderPlaced',
OrderShipped::class => 'handleOrderShipped',
OrderCancelled::class => 'handleOrderCancelled',
];
}
}Subscriber kaydi:
// EventServiceProvider
protected $subscribe = [
\App\Listeners\OrderEventSubscriber::class,
];Event-Listener deseni, bir aksiyonun birden fazla yan etkisini (side effect) birbirinden bagimsiz olarak yonetmenizi sağlar.
Notification Sistemi
Laravel'in bildirim sistemi tek bir sınıf uzerinden birden fazla kanalda (mail, SMS, database, broadcast) bildirim gondermenizi sağlar.
Notification Oluşturma
php artisan make:notification OrderShippedNotification// app/Notifications/OrderShippedNotification.php
namespace App\Notifications;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class OrderShippedNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
public Order $order
) {}
/**
* Hangi kanallardan bildirim gonderilecek
*/
public function via(object $notifiable): array
{
return ['mail', 'database'];
}
/**
* Mail bildirimi
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Siparissiniz kargoya verildi')
->greeting("Merhaba {$notifiable->name},")
->line("#{$this->order->id} numarali siparissiniz kargoya verilmistir.")
->action('Siparisi Goruntule', url("/orders/{$this->order->id}"))
->line('Bizi tercih ettiginiz icin tesekkurler!');
}
/**
* Database bildirimi
*/
public function toArray(object $notifiable): array
{
return [
'order_id' => $this->order->id,
'message' => "#{$this->order->id} numarali siparissiniz kargoya verildi.",
'url' => "/orders/{$this->order->id}",
];
}
}Bildirim Gonderme
use App\Notifications\OrderShippedNotification;
// Tek kullaniciya
$user->notify(new OrderShippedNotification($order));
// Birden fazla kullaniciya
use Illuminate\Support\Facades\Notification;
Notification::send($users, new OrderShippedNotification($order));Database Notifications
Database kanalini kullanmak için once notifications tablosunu olusturun:
php artisan notifications:table
php artisan migrateBildirimleri okuma:
// Okunmamis bildirimler
$unread = $user->unreadNotifications;
// Tum bildirimler
$all = $user->notifications;
// Okundu olarak isaretle
$user->unreadNotifications->markAsRead();
// Tek bildirimi okundu yap
$notification->markAsRead();
// Bildirim sayisi
$count = $user->unreadNotifications->count();SMS Bildirimi (Vonage/Twilio)
composer require laravel/vonage-notification-channel// Notification sinifinda
public function via(object $notifiable): array
{
return ['vonage']; // veya 'twilio'
}
public function toVonage(object $notifiable)
{
return (new \Illuminate\Notifications\Messages\VonageMessage)
->content("Siparis #{$this->order->id} kargoya verildi.");
}Model uzerinde telefon numarasi tanimlama:
// User model
public function routeNotificationForVonage(): string
{
return $this->phone_number;
}Broadcast Bildirimi (Gercek Zamanli)
public function via(object $notifiable): array
{
return ['broadcast', 'database'];
}
public function toBroadcast(object $notifiable)
{
return new \Illuminate\Notifications\Messages\BroadcastMessage([
'order_id' => $this->order->id,
'message' => 'Siparissiniz kargoya verildi.',
]);
}Bildirim sistemi tek bir sinifta tüm kanallari yonetmenizi sağlar.
ShouldQueueinterface'i ile bildirimler kuyruga alinarak performans artar.
Task Scheduling (Zamanlanmis Gorevler)
Laravel'in zamanlayicisi sayesinde sunucuda tek bir cron girisi yeterlidir. Tüm zamanlanmis gorevler routes/console.php veya app/Console/Kernel.php dosyasinda tanimlanir.
Cron Ayari (Sunucu)
Sunucuya tek bir cron satiri eklenir:
* * * * * cd /var/www/laravel && php artisan schedule:run >> /dev/null 2>&1Bu komut her dakika çalışır ve Laravel hangi gorevlerin zamaninin geldigini kontrol eder.
Gorev Tanimlama
Laravel 11+ (routes/console.php):
use Illuminate\Support\Facades\Schedule;
// Her gun gece 2'de yedek al
Schedule::command('backup:run')->dailyAt('02:00');
// Her saat basinda cache temizle
Schedule::command('cache:prune-stale-tags')->hourly();
// Her 5 dakikada bir kuyruk saglik kontrolu
Schedule::command('queue:monitor redis:default,redis:orders --max=100')
->everyFiveMinutes();
// Haftalik rapor olustur (Pazartesi saat 9)
Schedule::command('report:weekly')->weeklyOn(1, '09:00');
// Closure ile gorev
Schedule::call(function () {
DB::table('recent_views')->where('created_at', '<', now()->subDays(7))->delete();
})->daily();Laravel 10 ve oncesi (app/Console/Kernel.php):
protected function schedule(Schedule $schedule): void
{
$schedule->command('backup:run')->dailyAt('02:00');
$schedule->command('telescope:prune')->daily();
$schedule->command('queue:restart')->hourly();
}Zamanlama Sikliklari
| Metod | Açıklama |
|---|---|
->everyMinute() | Her dakika |
->everyFiveMinutes() | Her 5 dakika |
->everyFifteenMinutes() | Her 15 dakika |
->hourly() | Her saat |
->hourlyAt(15) | Her saat 15. dakikada |
->daily() | Her gun gece yarisi |
->dailyAt('13:00') | Her gun saat 13:00'te |
->weekly() | Haftada bir |
->weeklyOn(1, '8:00') | Pazartesi saat 8'de |
->monthly() | Ayda bir |
->quarterly() | Uc ayda bir |
->yearly() | Yilda bir |
->weekdays() | Sadece hafta ici |
->sundays() | Sadece Pazar |
Ek Ozellikler
// Cakismayı onle (ayni gorev tekrar baslamasin)
Schedule::command('report:generate')->hourly()->withoutOverlapping();
// Tek sunucuda calistir (birden fazla sunucuda)
Schedule::command('backup:run')->daily()->onOneServer();
// Baslangic ve bitis olaylarini logla
Schedule::command('emails:send')
->daily()
->before(function () {
logger()->info('Email gonderimi basliyor...');
})
->after(function () {
logger()->info('Email gonderimi tamamlandi.');
});
// Cikis koduna gore bildirim
Schedule::command('report:generate')
->daily()
->onFailure(function () {
// Hata bildirimi gonder
});Zamanlayiciyi Test Etme
# Hangi gorevlerin planlandigini gor
php artisan schedule:list
# Zamanlayiciyi bir kez calistir (test icin)
php artisan schedule:run
# Belirli bir komutu test et
php artisan schedule:testZamanlayici ile sunucuda onlarca cron satiri yerine tek bir giris yeterli olur. Tüm gorevler kod icinde tanimlanir, versiyon kontrolune alinir.
File Storage (Dosya Depolama)
Laravel'in dosya sistemi (Flysystem tabanli) farkli depolama suruculerini (local, S3, FTP) tek bir API ile kullanmanizi sağlar.
Yapılandırma
// config/filesystems.php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
],
],Public Disk ve Symbolic Link
php artisan storage:linkBu komut public/storage -> storage/app/public arasinda symbolic link olusturur.
Dosya Islemleri
use Illuminate\Support\Facades\Storage;
// -- Dosya yukleme --
$path = $request->file('avatar')->store('avatars', 'public');
// Sonuc: avatars/abc123.jpg
// Ozel isim ile kaydet
$path = $request->file('avatar')->storeAs(
'avatars',
$user->id . '.' . $request->file('avatar')->extension(),
'public'
);
// -- Dosya okuma --
$content = Storage::disk('local')->get('file.txt');
$exists = Storage::disk('public')->exists('avatars/1.jpg');
$url = Storage::disk('public')->url('avatars/1.jpg');
// -- Dosya silme --
Storage::disk('public')->delete('avatars/old.jpg');
Storage::disk('public')->delete(['file1.jpg', 'file2.jpg']);
// -- Dizin islemleri --
$files = Storage::disk('public')->files('avatars');
$dirs = Storage::disk('public')->directories();
Storage::disk('public')->makeDirectory('exports');
Storage::disk('public')->deleteDirectory('temp');
// -- Dosya kopyalama ve tasima --
Storage::copy('old/file.txt', 'new/file.txt');
Storage::move('old/file.txt', 'new/file.txt');S3'e Dosya Yukleme
composer require league/flysystem-aws-s3-v3 "^3.0"# .env
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_DEFAULT_REGION=eu-central-1
AWS_BUCKET=my-app-uploads// S3'e yukle
$path = $request->file('document')->store('documents', 's3');
// S3 URL'i al
$url = Storage::disk('s3')->url($path);
// Gecici (signed) URL olustur (ozel dosyalar icin)
$temporaryUrl = Storage::disk('s3')->temporaryUrl(
$path,
now()->addMinutes(30)
);Private & Public Dosyalar
// Public dosya -- herkes erisebilir
Storage::disk('public')->put('logos/logo.png', $content);
// Private dosya -- signed URL ile erisim
Storage::disk('s3')->put('invoices/inv-001.pdf', $content, 'private');
// Signed URL ile paylasim
$signedUrl = Storage::disk('s3')->temporaryUrl(
'invoices/inv-001.pdf',
now()->addHours(1)
);Dosya Validasyonu
$request->validate([
'avatar' => 'required|image|mimes:jpeg,png,webp|max:2048', // max 2 MB
'document' => 'required|file|mimes:pdf,doc,docx|max:10240', // max 10 MB
'csv' => 'required|file|mimetypes:text/csv|max:5120',
]);Dosya depolama için production ortaminda S3 veya benzeri nesne depolama (object storage) onerilir. Local disk sadece geliştirme ortaminda tercih edilmelidir.
Güvenlik (Security)
Mass Assignment Korumasi
Model uzerinde $fillable veya $guarded kullanarak toplu atama (mass assignment) saldirilarindan korunun:
// Sadece izin verilen alanlari belirt (onerilen)
protected $fillable = ['name', 'email', 'password'];
// veya yasakli alanlari belirt
protected $guarded = ['id', 'is_admin', 'role'];
// TEHLIKELI: Hic koruma yok -- kullanmayin!
protected $guarded = [];XSS Korumasi (Blade Escaping)
Blade sablonlarinda {{ }} kullanimi otomatik olarak HTML encode yapar:
{{-- Guvenli: XSS korumalı --}}
<p>{{ $user->name }}</p>
{{-- Tehlikeli: Ham HTML render eder, sadece guvenilir icerikte kullanin --}}
<p>{!! $trustedHtml !!}</p>
{!! !!}sadece admin panelinde veya tamamen kontrol ettiginiz icerikte kullanin.
CSRF Korumasi
Tüm POST/PUT/PATCH/DELETE formlarinda @csrf direktifini kullanin:
<form method="POST" action="/profile">
@csrf
@method('PUT')
<input type="text" name="name" value="{{ $user->name }}">
<button type="submit">Guncelle</button>
</form>API route'lari api middleware grubu icerisinde oldugundan CSRF dogrulamasi uygulanmaz (Sanctum token tabanli çalışır).
Rate Limiting
// routes/api.php veya RouteServiceProvider icinde
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Login icin daha katı limit
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->input('email') . $request->ip());
});
// Kullanim
Route::middleware(['throttle:login'])->post('/login', [AuthController::class, 'login']);Sifreleme (Encryption)
use Illuminate\Support\Facades\Crypt;
// Sifrele
$encrypted = Crypt::encryptString('hassas-veri');
// Coz
$decrypted = Crypt::decryptString($encrypted);
// Model uzerinde otomatik sifreleme (Laravel 11+)
protected function casts(): array
{
return [
'secret_token' => 'encrypted',
'settings' => 'encrypted:array',
];
}Signed URLs (Imzali URL'ler)
use Illuminate\Support\Facades\URL;
// Imzali URL olustur
$url = URL::signedRoute('unsubscribe', ['user' => $user->id]);
// Sureli imzali URL
$url = URL::temporarySignedRoute(
'download.invoice',
now()->addMinutes(30),
['invoice' => $invoice->id]
);
// Route taniminda dogrulama
Route::get('/unsubscribe/{user}', function (Request $request) {
if (! $request->hasValidSignature()) {
abort(401);
}
// ...
})->name('unsubscribe');
// Middleware ile dogrulama
Route::get('/download/{invoice}', [InvoiceController::class, 'download'])
->name('download.invoice')
->middleware('signed');Policy & Gate
Gate (basit yetkilendirme):
// AuthServiceProvider veya AppServiceProvider
Gate::define('manage-users', function (User $user) {
return $user->is_admin;
});
// Kullanim
if (Gate::allows('manage-users')) {
// admin islemleri
}
// Controller icinde
$this->authorize('manage-users');
// Blade icinde
@can('manage-users')
<a href="/admin/users">Kullanici Yonetimi</a>
@endcanPolicy (model bazli yetkilendirme):
php artisan make:policy ProductPolicy --model=Product// app/Policies/ProductPolicy.php
class ProductPolicy
{
public function view(User $user, Product $product): bool
{
return true; // herkes gorebilir
}
public function update(User $user, Product $product): bool
{
return $user->id === $product->user_id || $user->is_admin;
}
public function delete(User $user, Product $product): bool
{
return $user->id === $product->user_id;
}
}// Controller icinde kullanim
public function update(UpdateProductRequest $request, Product $product)
{
$this->authorize('update', $product);
// ...
}SQL Injection Korumasi
Eloquent ve Query Builder otomatik olarak parametre baglama (parameter binding) kullanir:
// Guvenli -- parametre baglama otomatik
User::where('email', $email)->first();
DB::table('users')->where('email', $email)->get();
// Tehlikeli -- ham SQL, kullanmayin
DB::select("SELECT * FROM users WHERE email = '$email'");
// Ham sorgu gerekiyorsa binding kullanin
DB::select('SELECT * FROM users WHERE email = ?', [$email]);Güvenlik Kontrol Listesi
| Alan | Kontrol |
|---|---|
| HTTPS | Tüm ortamlarda HTTPS zorunlu, HSTS aktif |
| Debug | Production'da APP_DEBUG=false |
| Anahtarlar | APP_KEY, token'lar duzgun rotate edilmeli |
| .env | Versiyon kontrolune eklenmemeli |
| Rate Limit | Ozellikle auth endpoint'lerinde aktif |
| CSRF | Web formlarda @csrf kullanimi |
| XSS | Blade {{ }} escaping kullanimi |
| SQL Injection | Eloquent/Query Builder parametre baglama |
| Mass Assignment | $fillable veya $guarded tanimi |
| CORS | Sadece izin verilen origin'ler |
| Dosya Yukleme | MIME tipi, boyut ve uzanti dogrulamasi |
| Sifre | Guclu sifre kurallari (min:8, letters, numbers) |
| Bagimlliklar | Duzenli composer audit |
Laravel Ekosistem & Paketler
Spatie Paketleri
Spatie, Laravel ekosisteminde en cok kullanilan acik kaynak paket gelistiricisidir.
spatie/laravel-permission (Rol ve Yetki Yönetimi): Kullanicilara roller ve izinler (permissions) atayarak yetkilendirme yapmanizi sağlar.
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate// Kullanim
$user->assignRole('editor');
$user->givePermissionTo('edit articles');
$user->hasRole('admin'); // true/false
$user->can('edit articles'); // true/false
// Blade
@role('admin')
<p>Admin paneli</p>
@endrolespatie/laravel-medialibrary (Dosya ve Medya Yönetimi): Modellere dosya, resim, video eklemenizi ve donusturmenizi (resize, crop) sağlar.
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate// Model uzerinde
class Product extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('images')->useDisk('public');
$this->addMediaCollection('thumbnail')->singleFile();
}
}
// Dosya ekleme
$product->addMediaFromRequest('image')->toMediaCollection('images');
// URL alma
$product->getFirstMediaUrl('thumbnail');spatie/laravel-activitylog (Aktivite Loglama): Model degisikliklerini (oluşturma, güncelleme, silme) otomatik olarak loglar.
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate// Model uzerinde
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Product extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'price', 'stock'])
->logOnlyDirty();
}
}
// Log kayitlarini oku
$activities = Activity::causedBy($user)->forSubject($product)->get();spatie/laravel-backup (Yedekleme): Veritabani ve dosya yedeklerini otomatik olusturur, S3/Google Drive gibi uzak depolara gonderir.
composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"// Zamanlanmis yedekleme
Schedule::command('backup:run')->dailyAt('02:00');
Schedule::command('backup:clean')->dailyAt('03:00');php artisan backup:run
php artisan backup:listspatie/laravel-sluggable (Otomatik Slug Oluşturma): Model alanlarindan otomatik slug (URL-dostu metin) uretir.
composer require spatie/laravel-sluggableuse Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Product extends Model
{
use HasSlug;
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug');
}
}
// Product::create(['name' => 'Laravel Rehberi']) -> slug: "laravel-rehberi"Laravel Debugbar
Geliştirme ortaminda HTTP istekleri, sorgular, gorunumler, route bilgisi ve daha fazlasini gorsel olarak izlemenizi sağlar.
composer require barryvdh/laravel-debugbar --devTarayicinin alt kisminda bir panel olarak gorunur. Production'da asla aktif etmeyin.
Laravel Telescope
Laravel'in resmi debugging ve izleme aracidir. Request, exception, log, query, job, mail, notification ve daha fazlasini izler.
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrateDashboard: /telescope adresinden erisim saglanir.
Laravel Horizon
Redis kuyruk islemlerini izlemek için gorsel dashboard (yukarda Queue bolumunde detayli anlatildi).
composer require laravel/horizon
php artisan horizon:install
php artisan migrateLaravel Pint (Kod Formatlama)
Laravel'in resmi PHP kod bicimleyicisi (code formatter). PHP-CS-Fixer uzerine kurulu, sifir yapılandırma ile çalışır.
# Kurulum (Laravel 10+ ile birlikte gelir)
composer require laravel/pint --dev
# Kodu formatla
./vendor/bin/pint
# Onizleme (degisiklikleri gosterir, uygulamaz)
./vendor/bin/pint --test
# Belirli dizin
./vendor/bin/pint app/ModelsLaravel Sail (Docker Geliştirme Ortami)
Laravel projelerini Docker ile calistirmak için resmi, hafif komut satiri aracidir.
# Yeni proje ile Sail
curl -s "https://laravel.build/my-app?with=mysql,redis" | bash
# Mevcut projeye Sail ekle
composer require laravel/sail --dev
php artisan sail:install
# Calistirma
./vendor/bin/sail up -d
./vendor/bin/sail artisan migrate
./vendor/bin/sail test
./vendor/bin/sail stopLivewire (Reaktif PHP Bilesenleri)
JavaScript yazmadan dinamik, reaktif arayuzler olusturmanizi sağlar. Blade sablonlari icerisinde çalışan PHP bilesenleridir.
composer require livewire/livewire// app/Livewire/SearchProducts.php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Product;
class SearchProducts extends Component
{
public string $search = '';
public function render()
{
$products = Product::where('name', 'like', "%{$this->search}%")
->take(10)
->get();
return view('livewire.search-products', compact('products'));
}
}<!-- resources/views/livewire/search-products.blade.php -->
<div>
<input type="text" wire:model.live="search" placeholder="Urun ara...">
<ul>
@foreach($products as $product)
<li>{{ $product->name }} - {{ $product->price }} TL</li>
@endforeach
</ul>
</div>Inertia.js (Modern SPA)
Laravel backend ile React/Vue/Svelte frontend'i tek bir monolith olarak birlestiren adaptordur. API yazmadan SPA gelistirebilirsiniz.
composer require inertiajs/inertia-laravel
npm install @inertiajs/react// Controller
use Inertia\Inertia;
class ProductController extends Controller
{
public function index()
{
return Inertia::render('Products/Index', [
'products' => Product::paginate(15),
]);
}
}// resources/js/Pages/Products/Index.jsx
export default function Index({ products }) {
return (
<div>
<h1>Urunler</h1>
{products.data.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}Socialite (OAuth / Sosyal Giris)
Google, GitHub, Facebook, Twitter gibi platformlardan OAuth ile giris yapmayi sağlar.
composer require laravel/socialite// config/services.php
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URI'),
],
// Route
Route::get('/auth/google', fn() => Socialite::driver('google')->redirect());
Route::get('/auth/google/callback', function () {
$googleUser = Socialite::driver('google')->user();
$user = User::updateOrCreate(
['email' => $googleUser->getEmail()],
['name' => $googleUser->getName(), 'google_id' => $googleUser->getId()]
);
Auth::login($user);
return redirect('/dashboard');
});Cashier (Abonelik ve Odeme)
Stripe veya Paddle ile abonelik yönetimi, tek seferlik odemeler ve faturalama islemleri.
composer require laravel/cashier
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate// Abonelik olustur
$user->newSubscription('default', 'price_monthly')->create($paymentMethodId);
// Abonelik kontrolu
$user->subscribed('default'); // true/false
$user->subscription('default')->onGracePeriod();
// Iptal
$user->subscription('default')->cancel();Scout (Tam Metin Arama)
Eloquent modellerine tam metin arama (full-text search) ozelligi ekler. Algolia, Meilisearch veya database driver kullanılabilir.
composer require laravel/scout
composer require meilisearch/meilisearch-php
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"// Model
use Laravel\Scout\Searchable;
class Product extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
];
}
}
// Arama
$products = Product::search('laptop')->get();
$products = Product::search('laptop')->where('is_active', true)->paginate(20);Reverb (WebSocket)
Laravel'in resmi WebSocket sunucusudur. Gercek zamanli (real-time) olay yayinlama için kullanilir.
composer require laravel/reverb
php artisan reverb:install# .env
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=my-app
REVERB_APP_KEY=my-key
REVERB_APP_SECRET=my-secret// Event
class OrderStatusUpdated implements ShouldBroadcast
{
public function broadcastOn(): array
{
return [new PrivateChannel("orders.{$this->order->user_id}")];
}
}// Frontend (Echo ile dinleme)
Echo.private(`orders.${userId}`)
.listen('OrderStatusUpdated', (e) => {
console.log('Siparis durumu:', e.order);
});Testing (Test)
Test Altyapisi
Laravel, Pest ve PHPUnit ile test yazmayi destekler. Pest daha modern ve okunabilir bir soz dizimi sunar.
# Pest kurulumu
composer require pestphp/pest --dev
php artisan pest:install
# Tum testleri calistir
php artisan test
# Belirli test dosyasi
php artisan test --filter=ProductTest
# Test suite
php artisan test --testsuite=Feature
php artisan test --testsuite=Unit
# Paralel calistirma
php artisan test --parallel
# Kapsam raporu (coverage)
php artisan test --coverage --min=80Feature Test (Entegrasyon Testi)
Feature testleri HTTP isteklerini simule ederek tüm katmanlari (route, controller, middleware, database) test eder.
// tests/Feature/Api/ProductTest.php
use App\Models\Product;
use App\Models\User;
use App\Models\Category;
beforeEach(function () {
$this->user = User::factory()->create();
$this->token = $this->user->createToken('api', ['products:create', 'products:update', 'products:delete'])->plainTextToken;
});
it('urun listesini getirir', function () {
Product::factory()->count(5)->create();
$this->withToken($this->token)
->getJson('/api/v1/products')
->assertOk()
->assertJsonCount(5, 'data');
});
it('yeni urun olusturur', function () {
$category = Category::factory()->create();
$payload = [
'name' => 'Test Urun',
'price' => 99.99,
'stock' => 50,
'category_id' => $category->id,
];
$this->withToken($this->token)
->postJson('/api/v1/products', $payload)
->assertCreated()
->assertJsonPath('data.name', 'Test Urun')
->assertJsonPath('data.price', '99.99');
$this->assertDatabaseHas('products', ['name' => 'Test Urun']);
});
it('gecersiz veri ile urun olusturamaz', function () {
$this->withToken($this->token)
->postJson('/api/v1/products', [])
->assertUnprocessable()
->assertJsonValidationErrors(['name', 'price', 'stock', 'category_id']);
});
it('urun gunceller', function () {
$product = Product::factory()->create();
$this->withToken($this->token)
->putJson("/api/v1/products/{$product->id}", ['name' => 'Guncellendi'])
->assertOk()
->assertJsonPath('data.name', 'Guncellendi');
});
it('urun siler', function () {
$product = Product::factory()->create();
$this->withToken($this->token)
->deleteJson("/api/v1/products/{$product->id}")
->assertNoContent();
$this->assertSoftDeleted('products', ['id' => $product->id]);
});
it('yetkisiz kullanici urun olusturamaz', function () {
$this->postJson('/api/v1/products', ['name' => 'Test'])
->assertUnauthorized();
});Unit Test
Unit testleri, is mantigi veya yardimci fonksiyonlari izole olarak test eder. Veritabani veya HTTP katmanina bagli degildir.
// tests/Unit/Services/ProductServiceTest.php
use App\Services\ProductService;
use App\Repositories\Contracts\ProductRepositoryInterface;
use App\Models\Product;
use Mockery;
it('urun olusturulurken slug uretilir', function () {
$mockRepo = Mockery::mock(ProductRepositoryInterface::class);
$mockRepo->shouldReceive('create')
->once()
->andReturn(new Product([
'name' => 'Test Urun',
'slug' => 'test-urun',
'price' => 100,
'stock' => 10,
'category_id' => 1,
]));
$service = new ProductService($mockRepo);
$product = $service->create([
'name' => 'Test Urun',
'price' => 100,
'stock' => 10,
'category_id' => 1,
]);
expect($product->slug)->toBe('test-urun');
});
it('fiyat hesaplamasini dogrular', function () {
$price = 100;
$discount = 15; // yuzde
$final = $price - ($price * $discount / 100);
expect($final)->toBe(85.0);
});Database Testing
// tests/TestCase.php veya Pest.php
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);RefreshDatabase vs. DatabaseTransactions:
| Trait | Davranis |
|---|---|
RefreshDatabase | Her testten once migration calistirir, veritabanini sifirlar |
DatabaseTransactions | Her testi transaction icinde calistirir, sonra rollback yapar |
RefreshDatabase önerilen yontemdir, ozellikle migration'larin test edilmesi gerektiginde.
Factories (Test Veri Uretimi)
// database/factories/ProductFactory.php
namespace Database\Factories;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->words(3, true),
'slug' => fake()->unique()->slug(),
'description' => fake()->paragraph(),
'price' => fake()->randomFloat(2, 10, 1000),
'stock' => fake()->numberBetween(0, 500),
'is_active' => true,
'category_id' => Category::factory(),
];
}
// State metodlari
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
public function outOfStock(): static
{
return $this->state(fn (array $attributes) => [
'stock' => 0,
]);
}
public function expensive(): static
{
return $this->state(fn (array $attributes) => [
'price' => fake()->randomFloat(2, 500, 5000),
]);
}
}Factory kullanimi:
// Temel kullanim
$product = Product::factory()->create();
// Ozel degerler ile
$product = Product::factory()->create(['name' => 'Ozel Urun', 'price' => 199.99]);
// State kullanimi
$product = Product::factory()->inactive()->create();
// Birden fazla olustur
$products = Product::factory()->count(10)->create();
// Iliskilerle birlikte
$products = Product::factory()
->count(5)
->has(ProductImage::factory()->count(3), 'images')
->create();Mocking (Taklit Nesneler)
use App\Services\PaymentService;
use Mockery;
it('odeme servisini mock eder', function () {
$mock = Mockery::mock(PaymentService::class);
$mock->shouldReceive('charge')
->once()
->with(100.00, 'tok_visa')
->andReturn(true);
$this->app->instance(PaymentService::class, $mock);
// Controller'i cagir, PaymentService mock kullanilacak
$this->postJson('/api/v1/orders', [
'amount' => 100.00,
'payment_token' => 'tok_visa',
])->assertCreated();
});
// Laravel Facade mock
use Illuminate\Support\Facades\Mail;
it('siparis sonrasi e-posta gonderilir', function () {
Mail::fake();
// Siparis olustur...
$this->postJson('/api/v1/orders', $payload)->assertCreated();
Mail::assertSent(OrderConfirmationMail::class, function ($mail) {
return $mail->hasTo('customer@example.com');
});
});
// Notification mock
use Illuminate\Support\Facades\Notification;
it('bildirim gonderildigini dogrular', function () {
Notification::fake();
// Islem yap...
Notification::assertSentTo($user, OrderShippedNotification::class);
Notification::assertCount(1);
});
// Queue mock
use Illuminate\Support\Facades\Queue;
it('job dispatch edildigini dogrular', function () {
Queue::fake();
// Islem yap...
Queue::assertPushed(ProcessOrderJob::class);
Queue::assertPushed(ProcessOrderJob::class, function ($job) {
return $job->order->id === 1;
});
});Test Best Practices
- Her test tek bir seyi dogrulamali (single assertion principle).
- Test isimleri ne test ettigini acikca belirtmeli (
it('yetkisiz kullanici urun silemez')). RefreshDatabasetrait'ini kullanarak testler arasi veri kirliligi onlenmeli.- Factory ve Seeder ile tutarli test verisi uretilmeli.
- Dis servisleri (payment, mail, sms) mutlaka mock'layin.
- CI/CD pipeline'da testlerin otomatik calistigindan emin olun.
# CI icin onerilen test komutu
php artisan test --parallel --coverage --min=80Tips & Best Practices
N+1 Sorgu Problemi ve Eager Loading
N+1 problemi, iliski verileri lazy load edildiginde her satir için ek sorgu atilmasidir.
// KOTU: N+1 problemi (100 post = 101 sorgu)
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // Her post icin ayri sorgu
}
// IYI: Eager loading (2 sorgu)
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name; // Onceden yuklendi
}
// Ic ice iliski
$posts = Post::with(['user', 'tags', 'comments.user'])->get();
// Kosullu eager loading
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true)->latest()->limit(5);
}])->get();N+1 tespiti için:
# Telescope ile sorgu izleme
# veya preventLazyLoading ile hata firlatma (dev ortam)// AppServiceProvider::boot()
Model::preventLazyLoading(! app()->isProduction());Bu ayar, lazy loading denendiginde exception firlatir ve N+1 sorunlarini erkenden yakalamanizi sağlar.
Chunk ile Büyük Veri Isleme
// Bellek tasirmamasi icin parcalar halinde isle
Product::chunk(500, function ($products) {
foreach ($products as $product) {
$product->update(['is_indexed' => true]);
}
});
// Lazy collection (bellek dostu)
Product::lazy()->each(function ($product) {
// Her kaydi teker teker isle
});
// Cursor (en dusuk bellek kullanimi)
foreach (Product::cursor() as $product) {
// ...
}| Yöntem | Bellek | Hiz | Kullanım |
|---|---|---|---|
all() | Yüksek | Hızlı | Küçük veri setleri |
chunk() | Düşük | Orta | Toplu güncelleme |
lazy() | Düşük | Orta | Iterator tabanlı işleme |
cursor() | En düşük | Yavas | Cok büyük veri setleri |
Cache Stratejisi
use Illuminate\Support\Facades\Cache;
// Basit cache
$products = Cache::remember('products.featured', 3600, function () {
return Product::where('is_featured', true)->with('category')->get();
});
// Tag'li cache (Redis gerektirir)
Cache::tags(['products'])->remember('products.all', 3600, function () {
return Product::all();
});
// Tag ile toplu temizleme
Cache::tags(['products'])->flush();
// Cache invalidation (model observer ile)
class ProductObserver
{
public function saved(Product $product): void
{
Cache::forget("products.{$product->id}");
Cache::tags(['products'])->flush();
}
public function deleted(Product $product): void
{
Cache::forget("products.{$product->id}");
Cache::tags(['products'])->flush();
}
}
// Atomic lock (es zamanli erisimi onle)
Cache::lock('process-order-' . $orderId, 10)->block(5, function () use ($order) {
// Tek seferde sadece bir islem
});Artisan Komutları Tablosu
| Komut | Açıklama |
|---|---|
php artisan serve | Geliştirme sunucusu baslat |
php artisan migrate | Migration'lari çalıştır |
php artisan migrate:fresh --seed | DB'yi sifirla ve seed et |
php artisan migrate:rollback | Son migration'i geri al |
php artisan make:model Product -mfsc | Model + migration + factory + seeder + controller |
php artisan make:controller Api/V1/ProductController --api | API controller |
php artisan make:request StoreProductRequest | Form request |
php artisan make:resource ProductResource | API resource |
php artisan make:job ProcessOrderJob | Queue job |
php artisan make:event OrderPlaced | Event |
php artisan make:listener SendConfirmation | Listener |
php artisan make:notification OrderShipped | Notification |
php artisan make:policy ProductPolicy --model=Product | Policy |
php artisan make:middleware EnsureAdmin | Middleware |
php artisan make:observer ProductObserver --model=Product | Observer |
php artisan make:factory ProductFactory --model=Product | Factory |
php artisan make:seeder ProductSeeder | Seeder |
php artisan make:test ProductTest | Feature test |
php artisan make:test ProductTest --unit | Unit test |
php artisan make:command GenerateReport | Custom artisan komutu |
php artisan tinker | REPL (interaktif PHP konsolu) |
php artisan route:list | Tüm route'lari listele |
php artisan config:cache | Config'i onbellege al |
php artisan optimize | Config + route + view cache |
php artisan optimize:clear | Tüm cache'leri temizle |
php artisan storage:link | Public storage link'i oluştur |
php artisan queue:work | Queue worker baslat |
php artisan horizon | Horizon dashboard baslat |
php artisan schedule:list | Zamanlanmis gorevleri listele |
php artisan schedule:run | Zamanlayiciyi bir kez çalıştır |
Genel Best Practices
- Controller ince tutun: Is mantigi service/action katmaninda olmali.
- Eloquent yerine raw SQL kullanmayin: SQL injection riski ve bakimi zor.
- Form Request kullanin: Validation mantigi controller'dan ayrilmali.
- Resource kullanin: API ciktisi her zaman tutarli olmali.
- Queue kullanin: E-posta, bildirim, dosya işleme gibi islemleri kuyruga atin.
- Cache kullanin: Sik degismeyen verileri onbellege alin.
- Test yazin: En azindan kritik is mantiklarini ve API endpoint'lerini test edin.
- Environment degiskenlerini kullanin: Config degerlerini
.envile yonetin. - Migration'lari atomic tutun: Her migration tek bir is yapmali.
- Log kullanin:
logger()veyaLog::ile önemli islemleri kaydedin.
Deployment, Optimization & Security
Preparing for Production (Dagitima Hazirlik)
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:yourkeyherephp artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize
composer install --no-dev --optimize-autoloader
chmod -R 775 storage bootstrap/cacheServer Setup -- Nginx
server {
listen 80;
server_name example.com;
root /var/www/html/laravel/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}Server Setup -- Apache
<VirtualHost *:80>
DocumentRoot /var/www/laravel/public
ServerName example.com
<Directory /var/www/laravel/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>Queue & Scheduler in Production
Supervisor setup (/etc/supervisor/conf.d/laravel-queue.conf):
[program:laravel-queue]
directory=/var/www/laravel
command=php artisan queue:work --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-queue.logsudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-queue:*Cron job:
* * * * * php /var/www/laravel/artisan schedule:run >> /dev/null 2>&1Docker & Makefile
docker-compose.yml:
services:
app:
build: .
volumes: [".:/var/www/html"]
env_file: [.env]
depends_on: [db, redis]
db:
image: mysql:8
environment:
MYSQL_DATABASE: fa_api
MYSQL_ROOT_PASSWORD: root
ports: ["3306:3306"]
redis:
image: redis:7
ports: ["6379:6379"]Makefile:
up: ## Start stack
docker compose up -d
down: ## Stop stack
docker compose down
migrate: ## Run migrations
docker compose exec app php artisan migrate
test: ## Run tests
docker compose exec app php artisan testCI/CD (GitHub Actions)
.github/workflows/tests.yml:
name: tests
on: [push, pull_request]
jobs:
php-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with: { php-version: '8.3', extensions: mbstring, pdo_mysql, redis }
- run: composer install --no-interaction --prefer-dist --no-progress
- run: cp .env.example .env
- run: php artisan key:generate
- run: php artisan test --testsuite=FeaturePerformance Optimization
Use Octane for long-running apps:
composer require laravel/octane
php artisan octane:start --server=swooleUse Redis for cache and queue:
CACHE_DRIVER=redis
QUEUE_CONNECTION=redisLogging & Monitoring (Loglama ve İzleme)
Telescope (development):
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrateAccess: /telescope
Sentry (production):
composer require sentry/sentry-laravelSENTRY_LARAVEL_DSN=https://examplePublicKey@o0.ingest.sentry.io/0Backup & Recovery (Yedekleme ve Kurtarma)
composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"Schedule automatic backups:
$schedule->command('backup:run')->dailyAt('02:00');Always store backups off-site (S3, Google Drive). (Yedekleri uzak bir konumda sakla.)
Security Checklist (Güvenlik Kontrol Listesi)
- [ ] HTTPS everywhere; HSTS
- [ ]
APP_DEBUG=falsein production - [ ] Rotate app keys & tokens;
.envnot committed - [ ] Rate limit auth endpoints
- [ ] Use abilities on tokens (least privilege)
- [ ] Validate and sanitize all inputs
- [ ] CORS allow-list exact origins
- [ ] Use CSRF protection in forms:
@csrf - [ ] Parameter binding for SQL (avoid injection)
- [ ] Store files off-app (S3) with signed URLs
- [ ] Enforce strong passwords (
min:8,letters,numbers) - [ ] Regular dependency updates (
composer audit)
Release & Ops Runbook
- Migrations: run with zero-downtime compatible changes (add nullable columns first, backfill in jobs, then make not-null)
- Config cache:
php artisan config:cache && php artisan route:cache - Horizon/Queue: scale workers on heavy jobs
- Backups: DB+storage daily; test restore quarterly
- Observability: Alerting on 5xx rate, job failures, slow queries
Final Deployment Checklist
| Task | Description |
|---|---|
APP_ENV=production | Environment set correctly |
APP_DEBUG=false | Debug disabled |
php artisan optimize | Configs, routes, and views cached |
storage:link | Public storage linked |
| Supervisor + Cron | Background jobs active |
| SSL active | HTTPS enabled |
| Backups configured | Daily automatic backups |
| Error tracking | Sentry/Telescope setup |
| Redis cache | Active and connected |
Resources & Developer Toolkit
Recommended Tools (Önerilen Araclar)
| Tool | Purpose | Website |
|---|---|---|
| VS Code | Code editor | code.visualstudio.com |
| Laravel Herd | Local Laravel environment | herd.laravel.com |
| Mailpit | Email testing | mailpit.axllent.org |
| Postman | API testing | postman.com |
| DB Browser for SQLite | Database viewer | sqlitebrowser.org |
| Git & GitHub | Version control | github.com |
| Sentry / Telescope | Error monitoring | sentry.io |
Quick Command Reference (Hızlı Komut Ozeti)
| Category | Command | Description |
|---|---|---|
| Project Setup | composer create-project laravel/laravel blog | Create a new Laravel project |
| Database | php artisan migrate:fresh --seed | Rebuild database and run seeders |
| Optimization | php artisan config:cache | Cache config for performance |
| Storage | php artisan storage:link | Create symbolic link for uploads |
| Deployment | composer install --no-dev --optimize-autoloader | Optimize app for production |
| Queue | php artisan queue:work | Process background jobs |
| Cache Clear | php artisan optimize:clear | Clear all cached data |
| Testing | php artisan test | Run unit and feature tests |
| Horizon | php artisan horizon | Manage queues via dashboard |
Learning Resources (Ek Kaynaklar)
| Resource | Description |
|---|---|
| Laravel Official Docs | Primary reference for Laravel framework |
| Laravel News | Official updates and tutorials |
| Laracasts | Premium video tutorials for Laravel |
| Spatie Open Source | Useful packages for Laravel |
| PHP.net | Official PHP documentation |
Next Steps (Sonraki Adimlar)
- Build your own Blog or CMS system
- Create a RESTful API and connect it to a React or Vue frontend
- Develop a mini e-commerce or booking platform
- Deploy to Hostinger, Vercel, or AWS
- Implement advanced caching, job queues, and notifications
The goal is not only to learn Laravel -- but to build something real and powerful. (Amac yalnizca Laravel'i ogrenmek degil, gercek ve guclu projeler gelistirmek.)
End of Guide.
Ilgili Rehberler
Backend
- Backend Genel Bakış
- Yazilim Mimarisi
- API Development
- ASP.NET Core Guide
- Node.js Rehberi
- Python Rehberi
- AI & LLM