Skip to content

📌 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)
bash
# Create a new project (Yeni proje olusturma)
composer create-project laravel/laravel my-app
cd my-app
php artisan serve

Now 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)

bash
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 migrate

Folder Structure (Klasor Yapisi)

DirectoryPurpose
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
.envEnvironment 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, Traits

MVC Architecture (MVC Mimarisi)

Model (M) — Represents your data and logic. Each database table usually has a model.

php
class Post extends Model {
    protected $fillable = ['title', 'content'];
}

View (V) — Responsible for displaying data to users using Blade templates.

html
<h1>{{ $post->title }}</h1>
<p>{{ $post->content }}</p>

Controller (C) — Handles requests, calls models, and returns views.

php
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.

ini
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=localhost

Tip: Never share .env publicly! (Asla .env dosyasini paylasma -- gizlidir.)

bash
php artisan config:clear
php artisan config:cache

Artisan CLI (Komut Satiri Araci)

Laravel's command-line tool is called Artisan. It helps automate repetitive tasks.

bash
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 EnsureAdmin

Artisan 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

php
// routes/web.php
Route::get('/', function () {
    return view('welcome');
});

Route::get('/posts', [PostController::class, 'index']);

Route Parameters

php
Route::get('/posts/{id}', [PostController::class, 'show']);
Route::get('/posts/{id}', [PostController::class, 'show'])->where('id', '[0-9]+');

Named Routes

php
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
return redirect()->route('dashboard');

Route Groups & Middleware

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

API Routing & Versioning

php
// 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

php
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.

php
// 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:

KatmanSorumluluk
ControllerHTTP istegini alir, response dondurur
ServiceIs mantigi (business logic) burada yashar
RepositoryVeritabani sorguları burada soyutlanir
ModelTablo tanimlari, iliskiler, cast'ler

Asagida bir Product (Urun) CRUD ornegi ile tüm katmanlari goreceksiniz.

Model

php
// 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

php
// 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;
}
php
// 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

php
// 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:

php
App\Providers\RepositoryServiceProvider::class,

Service Katmani

php
// 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)

php
// 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.',
        ];
    }
}
php
// 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)

php
// 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

php
// 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

php
// 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:

php
$request->validate([
    'title' => 'required|min:3',
    'content' => 'required'
]);

Form Request class:

php
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:

php
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.

php
class EnsureAdmin {
    public function handle($request, Closure $next) {
        if (!auth()->user() || !auth()->user()->is_admin) {
            return redirect('/');
        }
        return $next($request);
    }
}

Register middleware:

php
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)

  1. Laravel receives request via public/index.php
  2. Routes decide which controller handles it
  3. Middleware filters it (auth, CSRF, etc.)
  4. Controller processes and returns a response
  5. Response is sent to the client

Blade Templates (Sablonlar)

Laravel uses Blade, a lightweight templating engine.

blade
<!-- 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
@endsection

Blade Directives

blade
@if($user)
  <p>Welcome, {{ $user->name }}</p>
@else
  <p>Please log in.</p>
@endif

@foreach($posts as $post)
  <li>{{ $post->title }}</li>
@endforeach

Session & Flash Messages (Oturum ve Mesajlar)

php
session(['key' => 'value']);
echo session('key');

return redirect()->back()->with('success', 'Post created successfully!');

In Blade:

blade
@if(session('success'))
  <div class="alert alert-success">{{ session('success') }}</div>
@endif

Blade 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.

bash
php artisan make:migration create_posts_table
php
Schema::create('projects', function (Blueprint $t) {
    $t->id();
    $t->string('name');
    $t->text('description')->nullable();
    $t->timestamps();
    $t->softDeletes();
});
bash
php artisan migrate

Migrations keep database structure synchronized between developers. (Migration'lar, ekipteki herkesin ayni veritabani yapisini kullanmasini sağlar.)

Seeders & Factories (Test Verileri)

php
// 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();
}
bash
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.

php
// 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

php
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

php
class User extends Model {
    public function profile() {
        return $this->hasOne(Profile::class);
    }
}

$user = User::find(1);
$profile = $user->profile;

One-to-Many

php
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

php
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:

php
class Post extends Model {
    public function scopePublished($query) {
        return $query->where('status', 'published');
    }
}
$posts = Post::published()->get();

Global Scope:

php
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)

php
// 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)

php
protected $casts = [
    'is_published' => 'boolean',
    'meta' => 'array',
    'published_at' => 'datetime',
];

Soft Deletes (Yumusak Silme)

php
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();  // Restore

Events & Observers (Olaylar ve Gozlemciler)

Model Events:

php
class Post extends Model {
    protected static function booted() {
        static::creating(function ($post) {
            $post->slug = Str::slug($post->title);
        });
    }
}

Observers:

bash
php artisan make:observer PostObserver --model=Post
php
class PostObserver {
    public function created(Post $post) {
        Log::info('Post created: ' . $post->id);
    }
}

// Register in AppServiceProvider
Post::observe(PostObserver::class);

Pagination (Sayfalama)

php
$posts = Post::paginate(10);

In Blade:

blade
{{ $posts->links() }}

Query Builder

When you need flexibility beyond Eloquent:

php
$users = DB::table('users')
    ->where('active', 1)
    ->whereBetween('age', [18, 30])
    ->orderBy('name')
    ->get();

Performance Optimization (Performans Optimizasyonu)

Eager Loading -- avoids N+1 queries:

php
$posts = Post::with('user', 'tags')->get();

Chunking -- process large datasets:

php
Post::chunk(100, function($posts) {
    foreach ($posts as $post) {
        // process
    }
});

Caching:

php
$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:

php
$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.

php
// 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

php
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.

bash
php artisan make:provider ReportServiceProvider
php
class 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.

php
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)

php
use Illuminate\Support\Facades\Cache;
Cache::put('key', 'value', 3600);
echo Cache::get('key');

Custom Helpers (Ozel Yardimci Fonksiyonlar)

Create app/helpers.php:

php
function formatDate($date) {
    return \Carbon\Carbon::parse($date)->format('d/m/Y');
}

Include in composer.json:

json
"autoload": {
    "files": ["app/helpers.php"]
}

Then: composer dump-autoload

Event-Driven Architecture (Olay Tabanli Mimari)

bash
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
php
class 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

PatternPurposeExample
SingletonOne shared instanceapp()->singleton()
FactoryCreates objects dynamicallyModel Factories
RepositorySeparates data layerPostRepository
StrategySwitch between algorithmsPaymentService (Stripe/PayPal)
ObserverListens to model eventsPostObserver

Service Architecture Best Practices

  1. Keep controllers thin, move logic into services. (Controller'lari sade tut, mantigi servislere tasi.)
  2. Use interfaces for flexibility. (Esneklik için interface kullan.)
  3. Group related services in modules. (Ilgili servisleri moduller halinde topla.)
  4. Avoid Facades in testing; use dependency injection. (Testlerde Facade yerine DI tercih et.)
  5. Cache expensive operations. (Maliyetli islemleri onbellege al.)

Authentication (Kimlik Doğrulama)

Breeze (Web Authentication)

For traditional web apps with login/register:

bash
composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev
php artisan migrate

This scaffolds basic auth (login, register, forgot password) using Blade or React.

Sanctum (API Authentication)

For SPA or mobile API authentication:

bash
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Token issue and auth endpoints:

php
// 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:

bash
curl -X POST http://localhost/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"password"}'

Policies (Authorization)

php
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:

php
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:

json
{ "status":"success", "data": { "id": 1, "name": "Alpha" }, "meta": { "request_id":"..." } }

Error envelope:

json
{ "status":"error", "error": { "code":"VALIDATION_FAILED", "title":"Validation failed", "detail":"name is required", "source":{"field":"name"} } }

Global handler:

php
// 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)

php
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:

php
// 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:

php
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:

php
// 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):

php
$products = Product::orderBy('id')->cursorPaginate(20);
return ProductResource::collection($products);

cursorPaginate OFFSET kullanmaz, bu nedenle milyonlarca satirda bile hizlidir. Ancak toplam sayfa sayisi (last_page) bilgisi dondurmez.

Filtering / Sorting / Pagination / Includes

Query params convention:

  • filter[name]=alpha
  • sort=-created_at,name (minus = DESC)
  • page=1&per_page=20
  • include=tasks,user

Simple Filter class:

php
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:

php
$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.)

php
$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.)

php
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.php
php
return ['welcome' => 'Welcome to our site!'];

In Blade: {{ __('messages.welcome') }}

Logging, Observability & Rate Limits

  • api middleware already has throttle:api. Tune in RouteServiceProvider.
  • Add request id: middleware that adds X-Request-Id.
  • Send exceptions to Sentry in production.
php
Route::middleware(['throttle:60,1'])->group(function(){ /* ... */ });

API Docs (OpenAPI/Swagger)

Annotate controllers/resources with OpenAPI PHPDoc, then:

bash
php artisan l5-swagger:generate

Docs UI opens at /api/documentation (default). (Uretimde dokuman erisimini yetki ile kisitlayin.)

HTTP Status Code Mapping

CodeMeaning
200OK (GET)
201Created (POST create)
204No Content (DELETE/empty success)
400Bad Request (client syntax)
401Unauthorized (auth missing/invalid)
403Forbidden (authz denied)
404Not Found
422Validation Failed
429Too Many Requests
500Server Error

cURL Examples

bash
# 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 / UpdateXRequest per 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

ini
# .env
QUEUE_CONNECTION=redis

config/queue.php dosyasindan farkli kuyruk baglantilari ayarlanabilir: sync, database, redis, sqs.

Job Oluşturma

bash
php artisan make:job ProcessOrderJob
php
// 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

php
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

bash
# 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=3

Basarisiz Job'lar (Failed Jobs)

bash
# 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:flush

Laravel Horizon (Redis Queue Dashboard)

Horizon, Redis tabanli kuyruk islemlerini izlemek ve yonetmek için gorsel bir dashboard sağlar.

bash
composer require laravel/horizon
php artisan horizon:install
php artisan migrate
bash
# Horizon'u baslat
php artisan horizon

# Dashboard erisim: /horizon

Yapılandırma (config/horizon.php):

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):

php
// 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

bash
php artisan make:event OrderPlaced
php
// 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

bash
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
php artisan make:listener UpdateInventory --event=OrderPlaced
php artisan make:listener NotifyAdminAboutOrder --event=OrderPlaced
php
// 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));
    }
}
php
// 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)

php
// 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:

php
public function shouldDiscoverEvents(): bool
{
    return true;
}

Event Dispatch Etme

php
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:

php
// 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:

php
// 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

bash
php artisan make:notification OrderShippedNotification
php
// 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

php
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:

bash
php artisan notifications:table
php artisan migrate

Bildirimleri okuma:

php
// 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)

bash
composer require laravel/vonage-notification-channel
php
// 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:

php
// User model
public function routeNotificationForVonage(): string
{
    return $this->phone_number;
}

Broadcast Bildirimi (Gercek Zamanli)

php
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. ShouldQueue interface'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:

bash
* * * * * cd /var/www/laravel && php artisan schedule:run >> /dev/null 2>&1

Bu komut her dakika çalışır ve Laravel hangi gorevlerin zamaninin geldigini kontrol eder.

Gorev Tanimlama

Laravel 11+ (routes/console.php):

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):

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

MetodAçı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

php
// 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

bash
# 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:test

Zamanlayici 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

php
// 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'),
    ],
],
bash
php artisan storage:link

Bu komut public/storage -> storage/app/public arasinda symbolic link olusturur.

Dosya Islemleri

php
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

bash
composer require league/flysystem-aws-s3-v3 "^3.0"
ini
# .env
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_DEFAULT_REGION=eu-central-1
AWS_BUCKET=my-app-uploads
php
// 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

php
// 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

php
$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:

php
// 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:

blade
{{-- 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:

blade
<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

php
// 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)

php
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)

php
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):

php
// 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>
@endcan

Policy (model bazli yetkilendirme):

bash
php artisan make:policy ProductPolicy --model=Product
php
// 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;
    }
}
php
// 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:

php
// 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

AlanKontrol
HTTPSTüm ortamlarda HTTPS zorunlu, HSTS aktif
DebugProduction'da APP_DEBUG=false
AnahtarlarAPP_KEY, token'lar duzgun rotate edilmeli
.envVersiyon kontrolune eklenmemeli
Rate LimitOzellikle auth endpoint'lerinde aktif
CSRFWeb formlarda @csrf kullanimi
XSSBlade {{ }} escaping kullanimi
SQL InjectionEloquent/Query Builder parametre baglama
Mass Assignment$fillable veya $guarded tanimi
CORSSadece izin verilen origin'ler
Dosya YuklemeMIME tipi, boyut ve uzanti dogrulamasi
SifreGuclu sifre kurallari (min:8, letters, numbers)
BagimlliklarDuzenli 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.

bash
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
php
// 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>
@endrole

spatie/laravel-medialibrary (Dosya ve Medya Yönetimi): Modellere dosya, resim, video eklemenizi ve donusturmenizi (resize, crop) sağlar.

bash
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
php
// 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.

bash
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
php
// 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.

bash
composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
php
// Zamanlanmis yedekleme
Schedule::command('backup:run')->dailyAt('02:00');
Schedule::command('backup:clean')->dailyAt('03:00');
bash
php artisan backup:run
php artisan backup:list

spatie/laravel-sluggable (Otomatik Slug Oluşturma): Model alanlarindan otomatik slug (URL-dostu metin) uretir.

bash
composer require spatie/laravel-sluggable
php
use 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.

bash
composer require barryvdh/laravel-debugbar --dev

Tarayicinin 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.

bash
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Dashboard: /telescope adresinden erisim saglanir.

Laravel Horizon

Redis kuyruk islemlerini izlemek için gorsel dashboard (yukarda Queue bolumunde detayli anlatildi).

bash
composer require laravel/horizon
php artisan horizon:install
php artisan migrate

Laravel Pint (Kod Formatlama)

Laravel'in resmi PHP kod bicimleyicisi (code formatter). PHP-CS-Fixer uzerine kurulu, sifir yapılandırma ile çalışır.

bash
# 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/Models

Laravel Sail (Docker Geliştirme Ortami)

Laravel projelerini Docker ile calistirmak için resmi, hafif komut satiri aracidir.

bash
# 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 stop

Livewire (Reaktif PHP Bilesenleri)

JavaScript yazmadan dinamik, reaktif arayuzler olusturmanizi sağlar. Blade sablonlari icerisinde çalışan PHP bilesenleridir.

bash
composer require livewire/livewire
php
// 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'));
    }
}
blade
<!-- 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.

bash
composer require inertiajs/inertia-laravel
npm install @inertiajs/react
php
// Controller
use Inertia\Inertia;

class ProductController extends Controller
{
    public function index()
    {
        return Inertia::render('Products/Index', [
            'products' => Product::paginate(15),
        ]);
    }
}
jsx
// 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.

bash
composer require laravel/socialite
php
// 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.

bash
composer require laravel/cashier
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate
php
// 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.

bash
composer require laravel/scout
composer require meilisearch/meilisearch-php
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
php
// 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.

bash
composer require laravel/reverb
php artisan reverb:install
ini
# .env
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=my-app
REVERB_APP_KEY=my-key
REVERB_APP_SECRET=my-secret
php
// Event
class OrderStatusUpdated implements ShouldBroadcast
{
    public function broadcastOn(): array
    {
        return [new PrivateChannel("orders.{$this->order->user_id}")];
    }
}
javascript
// 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.

bash
# 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=80

Feature Test (Entegrasyon Testi)

Feature testleri HTTP isteklerini simule ederek tüm katmanlari (route, controller, middleware, database) test eder.

php
// 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.

php
// 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

php
// tests/TestCase.php veya Pest.php
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

RefreshDatabase vs. DatabaseTransactions:

TraitDavranis
RefreshDatabaseHer testten once migration calistirir, veritabanini sifirlar
DatabaseTransactionsHer testi transaction icinde calistirir, sonra rollback yapar

RefreshDatabase önerilen yontemdir, ozellikle migration'larin test edilmesi gerektiginde.

Factories (Test Veri Uretimi)

php
// 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:

php
// 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)

php
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

  1. Her test tek bir seyi dogrulamali (single assertion principle).
  2. Test isimleri ne test ettigini acikca belirtmeli (it('yetkisiz kullanici urun silemez')).
  3. RefreshDatabase trait'ini kullanarak testler arasi veri kirliligi onlenmeli.
  4. Factory ve Seeder ile tutarli test verisi uretilmeli.
  5. Dis servisleri (payment, mail, sms) mutlaka mock'layin.
  6. CI/CD pipeline'da testlerin otomatik calistigindan emin olun.
bash
# CI icin onerilen test komutu
php artisan test --parallel --coverage --min=80

Tips & Best Practices

N+1 Sorgu Problemi ve Eager Loading

N+1 problemi, iliski verileri lazy load edildiginde her satir için ek sorgu atilmasidir.

php
// 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:

bash
# Telescope ile sorgu izleme
# veya preventLazyLoading ile hata firlatma (dev ortam)
php
// 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

php
// 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öntemBellekHizKullanım
all()YüksekHızlıKüçük veri setleri
chunk()DüşükOrtaToplu güncelleme
lazy()DüşükOrtaIterator tabanlı işleme
cursor()En düşükYavasCok büyük veri setleri

Cache Stratejisi

php
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

KomutAçıklama
php artisan serveGeliştirme sunucusu baslat
php artisan migrateMigration'lari çalıştır
php artisan migrate:fresh --seedDB'yi sifirla ve seed et
php artisan migrate:rollbackSon migration'i geri al
php artisan make:model Product -mfscModel + migration + factory + seeder + controller
php artisan make:controller Api/V1/ProductController --apiAPI controller
php artisan make:request StoreProductRequestForm request
php artisan make:resource ProductResourceAPI resource
php artisan make:job ProcessOrderJobQueue job
php artisan make:event OrderPlacedEvent
php artisan make:listener SendConfirmationListener
php artisan make:notification OrderShippedNotification
php artisan make:policy ProductPolicy --model=ProductPolicy
php artisan make:middleware EnsureAdminMiddleware
php artisan make:observer ProductObserver --model=ProductObserver
php artisan make:factory ProductFactory --model=ProductFactory
php artisan make:seeder ProductSeederSeeder
php artisan make:test ProductTestFeature test
php artisan make:test ProductTest --unitUnit test
php artisan make:command GenerateReportCustom artisan komutu
php artisan tinkerREPL (interaktif PHP konsolu)
php artisan route:listTüm route'lari listele
php artisan config:cacheConfig'i onbellege al
php artisan optimizeConfig + route + view cache
php artisan optimize:clearTüm cache'leri temizle
php artisan storage:linkPublic storage link'i oluştur
php artisan queue:workQueue worker baslat
php artisan horizonHorizon dashboard baslat
php artisan schedule:listZamanlanmis gorevleri listele
php artisan schedule:runZamanlayiciyi bir kez çalıştır

Genel Best Practices

  1. Controller ince tutun: Is mantigi service/action katmaninda olmali.
  2. Eloquent yerine raw SQL kullanmayin: SQL injection riski ve bakimi zor.
  3. Form Request kullanin: Validation mantigi controller'dan ayrilmali.
  4. Resource kullanin: API ciktisi her zaman tutarli olmali.
  5. Queue kullanin: E-posta, bildirim, dosya işleme gibi islemleri kuyruga atin.
  6. Cache kullanin: Sik degismeyen verileri onbellege alin.
  7. Test yazin: En azindan kritik is mantiklarini ve API endpoint'lerini test edin.
  8. Environment degiskenlerini kullanin: Config degerlerini .env ile yonetin.
  9. Migration'lari atomic tutun: Her migration tek bir is yapmali.
  10. Log kullanin: logger() veya Log:: ile önemli islemleri kaydedin.

Deployment, Optimization & Security

Preparing for Production (Dagitima Hazirlik)

ini
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:yourkeyhere
bash
php 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/cache

Server Setup -- Nginx

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

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):

ini
[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.log
bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-queue:*

Cron job:

bash
* * * * * php /var/www/laravel/artisan schedule:run >> /dev/null 2>&1

Docker & Makefile

docker-compose.yml:

yaml
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:

make
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 test

CI/CD (GitHub Actions)

.github/workflows/tests.yml:

yaml
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=Feature

Performance Optimization

Use Octane for long-running apps:

bash
composer require laravel/octane
php artisan octane:start --server=swoole

Use Redis for cache and queue:

ini
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis

Logging & Monitoring (Loglama ve İzleme)

Telescope (development):

bash
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Access: /telescope

Sentry (production):

bash
composer require sentry/sentry-laravel
ini
SENTRY_LARAVEL_DSN=https://examplePublicKey@o0.ingest.sentry.io/0

Backup & Recovery (Yedekleme ve Kurtarma)

bash
composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"

Schedule automatic backups:

php
$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=false in production
  • [ ] Rotate app keys & tokens; .env not 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

TaskDescription
APP_ENV=productionEnvironment set correctly
APP_DEBUG=falseDebug disabled
php artisan optimizeConfigs, routes, and views cached
storage:linkPublic storage linked
Supervisor + CronBackground jobs active
SSL activeHTTPS enabled
Backups configuredDaily automatic backups
Error trackingSentry/Telescope setup
Redis cacheActive and connected

Resources & Developer Toolkit

ToolPurposeWebsite
VS CodeCode editorcode.visualstudio.com
Laravel HerdLocal Laravel environmentherd.laravel.com
MailpitEmail testingmailpit.axllent.org
PostmanAPI testingpostman.com
DB Browser for SQLiteDatabase viewersqlitebrowser.org
Git & GitHubVersion controlgithub.com
Sentry / TelescopeError monitoringsentry.io

Quick Command Reference (Hızlı Komut Ozeti)

CategoryCommandDescription
Project Setupcomposer create-project laravel/laravel blogCreate a new Laravel project
Databasephp artisan migrate:fresh --seedRebuild database and run seeders
Optimizationphp artisan config:cacheCache config for performance
Storagephp artisan storage:linkCreate symbolic link for uploads
Deploymentcomposer install --no-dev --optimize-autoloaderOptimize app for production
Queuephp artisan queue:workProcess background jobs
Cache Clearphp artisan optimize:clearClear all cached data
Testingphp artisan testRun unit and feature tests
Horizonphp artisan horizonManage queues via dashboard

Learning Resources (Ek Kaynaklar)

ResourceDescription
Laravel Official DocsPrimary reference for Laravel framework
Laravel NewsOfficial updates and tutorials
LaracastsPremium video tutorials for Laravel
Spatie Open SourceUseful packages for Laravel
PHP.netOfficial 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

Diger Kategoriler

Developer Guides & Technical References