Laravel Tinker: The Powerful Tool for Testing Your Laravel Code Quickly
Use Tinker REPL for quick testing and debugging.
Welcome to Part 2 of our Laravel Interview Questions series! While Part 1 covered the fundamentals, this guide dives deep into advanced Laravel concepts, with a special focus on Eloquent ORM. These are the questions that separate junior developers from senior ones, and mastering them will prepare you for even the most challenging Laravel interviews.
Answer: Eloquent ORM (Object-Relational Mapping) is Laravel's ActiveRecord implementation for database interaction. It provides an elegant, expressive syntax for working with databases using object-oriented models.
Key differences from Query Builder:
// Query Builder (fluent interface)
$users = DB::table('users')
->where('active', 1)
->orderBy('name')
->take(10)
->get();
// Eloquent ORM (ActiveRecord pattern)
$users = User::where('active', 1)
->orderBy('name')
->take(10)
->get();
// Eloquent advantages:
// - Model represents table row as object
// - Automatic timestamps, casting, relationships
// - Event observers, accessors/mutators
// - More readable and maintainable
Answer: Both methods enable eager loading, but with different usage patterns:
// with() - Eager load at query time
$users = User::with('posts')->get();
// Single query for users, single query for all related posts
// load() - Lazy eager load after query
$users = User::all();
$users->load('posts');
// Two separate queries
// loadMissing() - Load only if not already loaded
$users = User::all();
$users->loadMissing(['posts', 'profile']);
When to use which:
with(): When you know you'll need the relationship upfrontload(): When you need to load relationships after the initial queryloadMissing(): When conditionally loading relationshipsAnswer: The N+1 problem occurs when fetching related data in a loop, causing many unnecessary database queries:
// PROBLEM: N+1 Queries
$books = Book::all(); // 1 query
foreach ($books as $book) {
echo $book->author->name; // N queries (1 for each book)
}
// Total: 1 + N queries
// SOLUTION: Eager Loading with with()
$books = Book::with('author')->get(); // 2 queries total
// 1 query for books, 1 query for all related authors
Laravel's solutions:
with(), load()load() after initial querywithCount(), withSum(), etc.Answer: Scopes allow you to define common query constraints:
// Local Scope (must be called explicitly)
class User extends Model
{
public function scopeActive($query)
{
return $query->where('active', 1);
}
public function scopePopular($query, $minFollowers = 1000)
{
return $query->where('followers', '>=', $minFollowers);
}
}
// Usage
$activeUsers = User::active()->get();
$popularUsers = User::popular(5000)->get();
// Global Scope (applies automatically)
class User extends Model
{
protected static function booted()
{
static::addGlobalScope('active', function (Builder $builder) {
$builder->where('active', 1);
});
}
}
// Remove global scope
User::withoutGlobalScope('active')->get();
Answer: These features allow you to transform attribute values:
class User extends Model
{
// ACCESSOR: Transform when getting
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
// Usage: $user->full_name
// MUTATOR: Transform when setting
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
// Usage: $user->first_name = 'JOHN';
// ATTRIBUTE CASTING: Automatic type conversion
protected $casts = [
'is_admin' => 'boolean',
'settings' => 'array',
'birthday' => 'date',
'price' => 'decimal:2',
'created_at' => 'datetime:Y-m-d',
];
// Usage: automatically casts when accessed
}
Answer: Polymorphic relationships allow a model to belong to multiple other models on a single association:
// Database tables
// comments
// id - integer
// body - text
// commentable_id - integer
// commentable_type - string
// posts
// id - integer
// title - string
// videos
// id - integer
// title - string
// Comment model
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
// Post and Video models
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// Usage
$post = Post::find(1);
foreach ($post->comments as $comment) {
// Access comments
}
$comment = Comment::find(1);
$commentable = $comment->commentable; // Returns Post or Video
Answer: Also known as "polymorphic many-to-many", these allow a model to belong to multiple other models through an intermediate table:
// Database tables
// tags
// id - integer
// name - string
// taggables
// tag_id - integer
// taggable_id - integer
// taggable_type - string
// Post and Video models
class Post extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
class Video extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
// Tag model
class Tag extends Model
{
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
Answer: These methods allow you to query based on relationship existence:
// Get users who have at least one post
$users = User::has('posts')->get();
// Get users who have at least 3 posts
$users = User::has('posts', '>=', 3)->get();
// Get users who have posts with specific conditions
$users = User::whereHas('posts', function ($query) {
$query->where('published', true);
})->get();
// With nested relationships
$users = User::whereHas('posts.comments', function ($query) {
$query->where('approved', true);
})->get();
// Doesn't have relationship
$users = User::doesntHave('posts')->get();
Answer: Events allow you to hook into model lifecycle events:
// Events available
retrieved, creating, created, updating, updated,
saving, saved, deleting, deleted, restoring, restored
// Using closures
User::creating(function ($user) {
$user->api_token = Str::random(60);
});
// Using Observer class
php artisan make:observer UserObserver --model=User
class UserObserver
{
public function creating(User $user)
{
$user->api_token = Str::random(60);
}
public function created(User $user)
{
// Send welcome email
}
}
// Register in AppServiceProvider
public function boot()
{
User::observe(UserObserver::class);
}
Answer: The touch() method updates a model's updated_at timestamp:
$user = User::find(1);
$user->touch(); // Updates updated_at to current time
// Touch with specific timestamp
$user->touch('last_login_at');
// Touch parent's timestamp through relationship
$comment = Comment::find(1);
$comment->post->touch(); // Updates post's updated_at
// Automatic touching
class Comment extends Model
{
protected $touches = ['post'];
// Whenever comment is updated, post's updated_at is also updated
}
Answer: The replicate() method creates a new unsaved instance with copied attributes:
$user = User::find(1);
$newUser = $user->replicate();
// Exclude specific attributes
$newUser = $user->replicate()->fill([
'email' => 'new@example.com',
]);
// Replicate with relationships
$newUser = $user->replicate();
$newUser->push(); // Saves the user
foreach ($user->posts as $post) {
$newPost = $post->replicate();
$newPost->user_id = $newUser->id;
$newPost->save();
}
Answer: Different methods for creating/updating records:
// save() - Save model instance
$user = new User;
$user->name = 'John';
$user->email = 'john@example.com';
$user->save(); // Creates new record
$user = User::find(1);
$user->name = 'Jane';
$user->save(); // Updates existing record
// create() - Create with array (mass assignment)
$user = User::create([
'name' => 'John',
'email' => 'john@example.com',
'password' => bcrypt('password'),
]);
// update() - Update with array
User::where('active', 1)->update(['status' => 'verified']);
// updateOrCreate() - Update or create
$user = User::updateOrCreate(
['email' => 'john@example.com'], // Find by these attributes
['name' => 'John', 'active' => 1] // Update/create with these
);
Answer: Mass assignment allows setting multiple attributes at once. Security is handled through fillable/guarded properties:
class User extends Model
{
// Mass Assignment Protection
// OPTION 1: Fillable (allow list)
protected $fillable = [
'name', 'email', 'password'
];
// Only these fields can be mass assigned
// OPTION 2: Guarded (block list)
protected $guarded = [
'id', 'is_admin'
];
// All fields except these can be mass assigned
// OPTION 3: Guard all
protected $guarded = ['*'];
// No mass assignment allowed
// Force fill (bypass protection)
$user->forceFill([
'is_admin' => true
])->save();
}
Answer: These methods find or create records:
// firstOrNew() - Find or instantiate (not saved)
$user = User::firstOrNew(
['email' => 'john@example.com'],
['name' => 'John', 'active' => 1]
);
// Returns existing user or new unsaved instance
if (!$user->exists) {
$user->save();
}
// firstOrCreate() - Find or create and save
$user = User::firstOrCreate(
['email' => 'john@example.com'],
['name' => 'John', 'active' => 1]
);
// Returns existing user or creates new one
// firstOr() - Find or execute callback
$user = User::where('email', 'john@example.com')
->firstOr(function () {
return User::create([
'email' => 'john@example.com',
'name' => 'John',
]);
});
Answer: withCount() adds a count of related models to the query results:
// Basic usage
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
// Multiple counts
$posts = Post::withCount(['comments', 'likes'])->get();
// Access: $post->comments_count, $post->likes_count
// With conditions
$posts = Post::withCount([
'comments',
'comments as approved_comments_count' => function ($query) {
$query->where('approved', true);
}
])->get();
// Order by count
$posts = Post::withCount('comments')
->orderBy('comments_count', 'desc')
->get();
// Similar methods: withSum(), withAvg(), withMin(), withMax()
$posts = Post::withSum('comments', 'likes')->get();
// Access: $post->comments_sum_likes
Answer: Indexes improve query performance. Laravel supports various index types:
// Creating indexes in migrations
Schema::table('users', function (Blueprint $table) {
// Simple index
$table->index('email');
// Unique index
$table->unique('email');
// Composite index
$table->index(['first_name', 'last_name']);
// Full-text index (MySQL/PostgreSQL)
$table->fullText('body');
// Spatial index (MySQL/PostgreSQL)
$table->spatialIndex('location');
// Foreign key with index
$table->foreign('country_id')
->references('id')
->on('countries')
->onDelete('cascade');
});
// Dropping indexes
$table->dropIndex(['email']);
$table->dropUnique(['email']);
Answer: Transactions ensure a set of database operations complete successfully or roll back completely:
// Basic transaction
DB::transaction(function () {
$user = User::create(['email' => 'john@example.com']);
$user->orders()->create(['total' => 100]);
// If any operation fails, all are rolled back
});
// Manual transaction control
DB::beginTransaction();
try {
$user = User::create(['email' => 'john@example.com']);
$user->orders()->create(['total' => 100]);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
// Transaction levels (for deadlocks)
DB::transaction(function () {
// Nested transactions are converted to savepoints
}, 5); // 5 retries on deadlock
// Eloquent model transactions
User::createOrFail(['email' => 'john@example.com']);
// Automatically rolls back on failure
Answer: Advanced seeding with related models:
// DatabaseSeeder.php
public function run()
{
// Create 10 users
$users = User::factory(10)->create();
// Each user has 5 posts
$users->each(function ($user) {
$posts = Post::factory(5)->create([
'user_id' => $user->id
]);
// Each post has 3 comments
$posts->each(function ($post) use ($user) {
Comment::factory(3)->create([
'post_id' => $post->id,
'user_id' => $user->id
]);
});
});
}
// Using states in factories
User::factory()
->count(5)
->has(
Post::factory()
->count(3)
->has(
Comment::factory()->count(2)
)
)
->create();
Answer: Raw expressions allow executing raw SQL within Laravel's query builder:
// Raw select
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
// Raw where
$users = DB::table('users')
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
// Raw joins
$orders = DB::table('orders')
->select('orders.*', DB::raw('SUM(items.price) as total'))
->join('items', 'orders.id', '=', 'items.order_id')
->groupBy('orders.id')
->get();
// Order by raw
$users = User::orderByRaw('updated_at - created_at DESC')->get();
// Raw insert
DB::insert('insert into users (id, name) values (?, ?)', [1, 'John']);
// WARNING: Always use parameter binding to prevent SQL injection
// DON'T: DB::raw("WHERE name = '$name'")
// DO: DB::raw("WHERE name = ?", [$name])
Answer: Laravel provides query result caching:
// Basic caching
$users = Cache::remember('users', $seconds = 3600, function () {
return DB::table('users')->get();
});
// Eloquent caching
$users = User::remember($seconds)->get();
// Cache with tags
$users = Cache::tags(['users', 'active'])
->remember('active_users', $seconds, function () {
return User::active()->get();
});
// Automatic cache clearing on model events
class User extends Model
{
protected static function booted()
{
static::saved(function () {
Cache::tags(['users'])->flush();
});
static::deleted(function () {
Cache::tags(['users'])->flush();
});
}
}
// Cache locking (prevent cache stampede)
$value = Cache::lock('foo')->get(function () {
return DB::table('users')->get();
});
Answer: Customize how route parameters are resolved to models:
// Implicit binding with key
Route::get('/api/posts/{post:slug}', function (Post $post) {
return $post;
});
// Multiple parameters
Route::get('/api/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
return $post;
});
// Custom resolution logic
// In RouteServiceProvider
public function boot()
{
parent::boot();
// Custom binding
Route::bind('post', function ($value) {
return Post::where('slug', $value)
->orWhere('id', $value)
->firstOrFail();
});
// Model binding with scope
Route::model('post', Post::class, function () {
return Post::published();
});
}
// Scoped binding
Route::bind('post', function ($value, $route) {
return $route->user->posts()
->where('slug', $value)
->firstOrFail();
});
Answer: Rate limiting controls request frequency:
// In routes/web.php or routes/api.php
Route::middleware(['throttle:api'])->group(function () {
Route::get('/user', function () {
// ...
});
});
// Custom rate limiting
// In App\Providers\RouteServiceProvider
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('uploads', function (Request $request) {
return $request->user()->vipCustomer()
? Limit::none()
: Limit::perMinute(10)->by($request->ip());
});
// Dynamic limits
RateLimiter::for('global', function (Request $request) {
return [
Limit::perMinute(1000),
Limit::perMinute(100)->by($request->ip()),
Limit::perMinute(50)->by($request->user()?->id),
];
});
}
// Using in routes
Route::middleware(['throttle:uploads'])->post('/photo', function () {
// ...
});
Answer: Signed routes create URLs with a signature to prevent URL tampering:
// Creating signed routes
Route::get('/unsubscribe/{user}', function (Request $request, User $user) {
if (!$request->hasValidSignature()) {
abort(403);
}
// Unsubscribe logic
})->name('unsubscribe');
// Generate signed URL
$url = URL::signedRoute('unsubscribe', ['user' => 1]);
// Result: /unsubscribe/1?signature=...
// Temporary signed URL (expires)
$url = URL::temporarySignedRoute(
'unsubscribe',
now()->addMinutes(30),
['user' => 1]
);
// In middleware
Route::get('/unsubscribe/{user}', function (User $user) {
// Automatically validated by middleware
})->middleware('signed');
// Validate in controller
public function unsubscribe(Request $request, User $user)
{
if (!$request->hasValidSignature()) {
abort(403);
}
// ...
}
Answer: Route caching compiles all routes into a single file for performance:
# Generate route cache
php artisan route:cache
# Clear route cache
php artisan route:clear
# List routes (shows cached routes)
php artisan route:list
Benefits:
Limitations:
// BAD: Cannot cache
Route::get('/user', function () {
return view('user');
});
// GOOD: Can cache
Route::get('/user', [UserController::class, 'index']);
Answer: Macroable traits allow adding methods to classes at runtime:
// Adding macros to existing classes
Route::macro('admin', function () {
return Route::prefix('admin')
->middleware(['auth', 'admin'])
->group(function () {
// Admin routes
});
});
// Usage
Route::admin()->group(function () {
Route::get('/dashboard', 'AdminController@dashboard');
});
// Macro with parameters
Response::macro('caps', function ($value) {
return Response::make(strtoupper($value));
});
// Usage
return response()->caps('hello'); // Returns "HELLO"
// Macroable classes in Laravel:
// - Illuminate\Support\Collection
// - Illuminate\Support\Str
// - Illuminate\Http\Request
// - Illuminate\Routing\ResponseFactory
// - Illuminate\Routing\Route
Answer: Contextual binding allows different implementations based on usage context:
// In AppServiceProvider
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('photos');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('videos');
});
// With parameters
$this->app->when(ReportGenerator::class)
->needs('$timezone')
->give(config('app.timezone'));
// Tagging
$this->app->tag([PhotoFilter::class, VideoFilter::class], 'filters');
$this->app->when(ImageProcessor::class)
->needs('$filters')
->giveTagged('filters');
// Primitive binding
$this->app->when(OrderService::class)
->needs('$retryAttempts')
->give(3);
Answer: Deferred providers only load when their services are actually needed:
class MyServiceProvider extends ServiceProvider
{
// Register as deferred
protected $defer = true;
// Register services
public function register()
{
$this->app->singleton(MyService::class, function ($app) {
return new MyService($app['config']);
});
}
// Define which services this provider registers
public function provides()
{
return [MyService::class];
}
}
// Benefits:
// - Faster application boot time
// - Reduced memory usage
// - Only loaded when service is requested
// When to use:
// - For services not used on every request
// - Heavy services with slow initialization
// - Optional features that might not be used
Answer: Two patterns for dependency injection in Laravel:
// CONSTRUCTOR INJECTION (Recommended)
class OrderController extends Controller
{
protected $orderService;
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
public function show($id)
{
return $this->orderService->find($id);
}
}
// METHOD INJECTION
class OrderController extends Controller
{
public function show(OrderService $orderService, $id)
{
// Service is injected into method
return $orderService->find($id);
}
}
// When to use each:
// Constructor Injection:
// - When dependency is used in multiple methods
// - For required dependencies
// - Better for testing
// Method Injection:
// - When dependency is only used in one method
// - For optional dependencies
// - When you want to keep constructor clean
Answer: Container events allow you to hook into service resolution:
// Listening for service resolution
$this->app->resolving(function ($object, $app) {
// Called when any service is resolved
});
$this->app->resolving(Logger::class, function ($logger, $app) {
// Called only when Logger is resolved
$logger->setTimezone($app['config']['app.timezone']);
});
// After resolving event
$this->app->afterResolving(function ($object, $app) {
// Called after service is resolved
});
// Practical example
$this->app->resolving(Service::class, function ($service, $app) {
if ($app->environment('local')) {
$service->enableDebug();
}
});
// In middleware or controllers
app()->resolving(function ($object) {
if (method_exists($object, 'initialize')) {
$object->initialize();
}
});
Answer: The extend() method allows modifying resolved services:
// Extending a singleton
$this->app->extend(Logger::class, function ($logger, $app) {
// Add custom handler to existing logger
$logger->pushHandler(new CustomHandler());
return $logger;
});
// Extending a binding
$this->app->extend('cache', function ($cache, $app) {
// Wrap cache with custom decorator
return new CacheDecorator($cache);
});
// Practical example: Adding headers to mailer
$this->app->extend('mail.manager', function ($mailManager, $app) {
$mailManager->alwaysFrom('noreply@example.com', 'Example App');
$mailManager->alwaysReplyTo('support@example.com');
return $mailManager;
});
// Extending with interface
$this->app->extend(RepositoryInterface::class, function ($repository, $app) {
return new CachedRepository($repository, $app['cache.store']);
});
Answer: Using transactions to isolate tests and keep database clean:
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
public function test_basic_example()
{
// Test runs in transaction
$user = User::factory()->create();
// After test, transaction is rolled back
// Database returns to original state
}
}
// Without RefreshDatabase trait
public function setUp(): void
{
parent::setUp();
$this->beginDatabaseTransaction();
}
public function tearDown(): void
{
$this->rollbackDatabaseTransaction();
parent::tearDown();
}
// Testing with multiple connections
DB::connection('test')->beginTransaction();
// Test code
DB::connection('test')->rollBack();
Answer: Mocking replaces real objects with test doubles:
// Mocking with Mockery
$mock = Mockery::mock(Service::class);
$mock->shouldReceive('process')
->once()
->with(['data'])
->andReturn('result');
// Laravel's helper methods
$mock = $this->mock(Service::class, function ($mock) {
$mock->shouldReceive('process')
->once()
->andReturn('mocked result');
});
// Partial mocks
$mock = $this->partialMock(Service::class, function ($mock) {
$mock->shouldReceive('process')
->andReturn('mocked');
// Other methods use real implementation
});
// Stubbing (simpler than mocking)
$stub = $this->createStub(Service::class);
$stub->method('process')
->willReturn('stubbed result');
// Mocking facades
Cache::shouldReceive('get')
->with('key')
->once()
->andReturn('value');
// Spying (verify calls without affecting behavior)
$spy = $this->spy(Service::class);
// Run code
$spy->shouldHaveReceived('process')
->with(['data'])
->once();
Answer: Creating custom assertions for cleaner tests:
// Custom assertion class
class CustomAssertions
{
public function assertUserIsActive($user, $message = '')
{
Assert::assertTrue(
$user->isActive(),
$message ?: "The user [{$user->id}] is not active."
);
}
public function assertResponseContainsJson($response, $data)
{
$response->assertJson($data);
}
}
// Using in tests
public function test_user_status()
{
$user = User::factory()->create(['active' => true]);
$this->assertUserIsActive($user);
}
// Macros for TestResponse
TestResponse::macro('assertHasPaginatedData', function ($key) {
$this->assertJsonStructure([
'data' => [$key],
'links',
'meta',
]);
return $this;
});
// Usage
$response->assertHasPaginatedData('users');
Answer: States allow creating model instances with specific attributes:
// Defining states
class UserFactory extends Factory
{
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'active' => true,
];
}
public function inactive()
{
return $this->state(function (array $attributes) {
return [
'active' => false,
];
});
}
public function admin()
{
return $this->state([
'is_admin' => true,
]);
}
public function withSubscription()
{
return $this->afterCreating(function (User $user) {
$user->subscription()->create([
'plan' => 'premium',
'ends_at' => now()->addYear(),
]);
});
}
}
// Using states
$inactiveUser = User::factory()->inactive()->create();
$adminUser = User::factory()->admin()->create();
$subscribedUser = User::factory()->withSubscription()->create();
// Multiple states
$user = User::factory()
->inactive()
->admin()
->create();
// State with parameters
public function withBalance($amount)
{
return $this->state([
'balance' => $amount,
]);
}
Answer: Methods to bypass middleware and exception handling in tests:
// Without middleware
public function test_api_endpoint()
{
$this->withoutMiddleware();
// Test API endpoint without auth middleware
$response = $this->get('/api/users');
$response->assertOk();
}
// Without specific middleware
$this->withoutMiddleware([
VerifyCsrfToken::class,
Authenticate::class,
]);
// Without exception handling
public function test_validation_error()
{
$this->withoutExceptionHandling();
// Exception will be thrown instead of caught
$response = $this->post('/users', []);
// Will see actual validation exception
}
// Without event handling
$this->withoutEvents();
// Without job dispatching
$this->withoutJobs();
// Without notification sending
$this->withoutNotifications();
Answer: Two patterns for organizing related jobs:
// JOB CHAINS (Sequential execution)
ProcessPodcast::withChain([
new OptimizePodcast,
new ReleasePodcast,
])->dispatch();
// With delay between jobs
ProcessPodcast::withChain([
(new OptimizePodcast)->delay(now()->addMinutes(10)),
new ReleasePodcast,
])->dispatch();
// JOB BATCHES (Parallel execution, with callbacks)
$batch = Bus::batch([
new ProcessPodcast(1),
new ProcessPodcast(2),
new ProcessPodcast(3),
])->then(function (Batch $batch) {
// All jobs completed successfully
})->catch(function (Batch $batch, Throwable $e) {
// First batch job failure detected
})->finally(function (Batch $batch) {
// The batch has finished executing
})->dispatch();
// Batch progress
Route::get('/batch/{batchId}', function (string $batchId) {
return Bus::findBatch($batchId);
});
// Differences:
// Chains: Sequential, one job after another
// Batches: Parallel, with progress tracking and callbacks
Answer: Job middleware allows you to wrap job execution with custom logic:
// Creating job middleware
php artisan make:middleware RateLimited
class RateLimited
{
public function handle($job, $next)
{
Redis::throttle('key')
->block(0)->allow(1)->every(5)
->then(function () use ($job, $next) {
// Lock obtained...
$next($job);
}, function () use ($job) {
// Could not obtain lock...
$job->release(5);
});
}
}
// Using middleware in jobs
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function middleware()
{
return [new RateLimited];
}
}
// Multiple middleware
public function middleware()
{
return [
new RateLimited,
new WithoutOverlapping,
];
}
// Global job middleware
// In AppServiceProvider
protected $middleware = [
\Illuminate\Queue\Middleware\RateLimited::class,
];
// Using WithoutOverlapping middleware
(new ProcessPodcast)->through([
new WithoutOverlapping('podcast-processing'),
])->dispatch();
Answer: Handling failed jobs with retry logic:
// Configuring retries
class ProcessPodcast implements ShouldQueue
{
public $tries = 3; // Max attempts
public $maxExceptions = 3; // Max exceptions
public $timeout = 120; // Seconds
public $backoff = 60; // Seconds between retries
// Dynamic backoff
public function backoff()
{
return [1, 5, 10, 30]; // Exponential backoff
}
// Retry until
public function retryUntil()
{
return now()->addMinutes(10);
}
// Handle failure
public function failed(Throwable $exception)
{
// Send notification, log, etc.
}
}
// Manual retry
$failedJob = DB::table('failed_jobs')->find(1);
(new ProcessPodcast)->dispatch();
DB::table('failed_jobs')->where('id', 1)->delete();
// Automatic retry of all failed jobs
php artisan queue:retry all
// Pruning failed jobs
php artisan queue:prune-failed --hours=48
Answer: Laravel Horizon provides a dashboard and metrics for Redis queues:
// Configuring Horizon
// config/horizon.php
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'processes' => 10,
'tries' => 3,
'timeout' => 60,
],
],
],
// Metrics collection
Horizon::routeMailNotificationsTo('admin@example.com');
Horizon::routeSlackNotificationsTo('slack-webhook-url');
// Custom metrics
Horizon::metrics(function () {
return [
'jobs_per_minute' => Redis::get('jobs_per_minute'),
'pending_jobs' => Redis::llen('queues:default'),
];
});
// Dashboard authorization
Horizon::auth(function ($request) {
return $request->user()->isAdmin();
});
// Snapshot scheduling
// In App\Console\Kernel
protected function schedule(Schedule $schedule)
{
$schedule->command('horizon:snapshot')->everyFiveMinutes();
}
Answer: Running queue workers in production:
# Basic worker
php artisan queue:work
# With options
php artisan queue:work --queue=high,default --tries=3 --timeout=90
# Daemon mode (for production)
php artisan queue:work --daemon
# Supervisor configuration
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/your-app/worker.log
stopwaitsecs=3600
# Multiple queues with different workers
[program:laravel-worker-high]
command=php /var/www/your-app/artisan queue:work --queue=high --tries=3
[program:laravel-worker-default]
command=php /var/www/your-app/artisan queue:work --queue=default --tries=1
Answer: CSRF protection for stateless APIs:
// Excluding API routes from CSRF
// In App\Http\Middleware\VerifyCsrfToken
protected $except = [
'api/*',
'stripe/*',
];
// Using API tokens
// In User model
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
}
// Creating tokens
$token = $user->createToken('api-token', ['server:update']);
// API authentication
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
// Using Passport (OAuth2)
php artisan passport:install
// Protecting routes
Route::get('/user', function () {
// Only accessible with valid token
})->middleware('auth:api');
// Rate limiting APIs
Route::middleware(['throttle:api'])->group(function () {
Route::get('/user', function () {
// ...
});
});
Answer: Cross-Site Scripting protection in Blade templates:
// Automatic escaping in Blade
{{ $userInput }} // Escaped automatically
// Raw output (use with caution)
{!! $htmlContent !!}
// Clean user input
use Illuminate\Support\Str;
$clean = Str::of($dirty)
->stripTags()
->limit(255);
// In validation
$request->validate([
'content' => 'required|string|max:5000',
]);
// Content Security Policy
// In middleware
class AddSecurityHeaders
{
public function handle($request, $next)
{
$response = $next($request);
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'"
);
return $response;
}
}
// Sanitizing HTML with package
composer require mews/purifier
Answer: Two different security mechanisms:
// ENCRYPTION (Two-way, with app key)
use Illuminate\Support\Facades\Crypt;
// Encrypt
$encrypted = Crypt::encryptString('secret');
// Can be decrypted back to original
// Decrypt
$decrypted = Crypt::decryptString($encrypted);
// HASHING (One-way, for passwords)
use Illuminate\Support\Facades\Hash;
// Hash password
$hashed = Hash::make('password');
// Cannot be reversed
// Verify hash
if (Hash::check('password', $hashed)) {
// Password matches
}
// Rehashing (when algorithm improves)
if (Hash::needsRehash($hashed)) {
$newHashed = Hash::make('password');
}
// When to use:
// Encryption: Sensitive data that needs retrieval
// Hashing: Passwords, never need to retrieve original
Answer: Caching database queries for performance:
// Basic query caching
$users = Cache::remember('users', $seconds = 3600, function () {
return DB::table('users')->get();
});
// Automatic model caching (with package)
composer require rennokki/laravel-eloquent-query-cache
// Using in models
class User extends Model
{
use QueryCacheable;
public $cacheFor = 3600; // Cache for 1 hour
protected static $flushCacheOnUpdate = true;
}
// Cache tags (Redis/Memcached only)
$users = Cache::tags(['users', 'active'])
->remember('active_users', 3600, function () {
return User::active()->get();
});
// Clear tagged cache
Cache::tags(['users'])->flush();
// Cache locking (prevent cache stampede)
$value = Cache::lock('key', 10)->get(function () {
return DB::table('users')->get();
});
Answer: Advanced eager loading techniques:
// Nested eager loading
$books = Book::with('author.country')->get();
// Constraining eager loads
$users = User::with(['posts' => function ($query) {
$query->where('published', true);
}])->get();
// Lazy eager loading with constraints
$users = User::all();
$users->load(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}]);
// Multiple relationships with constraints
$users = User::with([
'posts' => function ($query) {
$query->where('published', true);
},
'posts.comments' => function ($query) {
$query->where('approved', true);
}
])->get();
// Eager load counts with constraints
$users = User::withCount([
'posts',
'posts as published_posts_count' => function ($query) {
$query->where('published', true);
}
])->get();
// Avoiding N+1 with subqueries
$users = User::addSelect(['last_post_date' => Post::select('created_at')
->whereColumn('user_id', 'users.id')
->latest()
->limit(1)
])->get();
Answer: Service providers for Laravel packages:
// Creating a package service provider
php artisan make:provider PackageServiceProvider
// Basic structure
class PackageServiceProvider extends ServiceProvider
{
public function register()
{
// Register bindings
$this->app->singleton(PackageClass::class, function () {
return new PackageClass(config('package.key'));
});
}
public function boot()
{
// Load routes
$this->loadRoutesFrom(__DIR__.'/routes/web.php');
// Load views
$this->loadViewsFrom(__DIR__.'/views', 'package');
// Load migrations
$this->loadMigrationsFrom(__DIR__.'/database/migrations');
// Load translations
$this->loadTranslationsFrom(__DIR__.'/lang', 'package');
// Publish assets
$this->publishes([
__DIR__.'/config/package.php' => config_path('package.php'),
], 'package-config');
}
}
// Deferred provider for performance
protected $defer = true;
public function provides()
{
return [PackageClass::class];
}
Answer: Automatic package registration without manual service provider addition:
// In package's composer.json
{
"extra": {
"laravel": {
"providers": [
"Vendor\\Package\\PackageServiceProvider"
],
"aliases": {
"Package": "Vendor\\Package\\Facades\\Package"
}
}
}
}
// Disabling auto-discovery
// In application's composer.json
{
"extra": {
"laravel": {
"dont-discover": [
"barryvdh/laravel-debugbar"
]
}
}
}
// Manual discovery commands
php artisan package:discover
php artisan package:discover --ansi
// Conditional service providers
class PackageServiceProvider extends ServiceProvider
{
public function register()
{
// Only register if condition is met
if ($this->app->environment('local')) {
$this->app->register(LocalServiceProvider::class);
}
}
}
Answer: Creating facades for packages:
// Facade class
namespace Vendor\Package\Facades;
use Illuminate\Support\Facades\Facade;
class Package extends Facade
{
protected static function getFacadeAccessor()
{
return 'package';
}
}
// Service binding
$this->app->singleton('package', function ($app) {
return new PackageManager($app['config']);
});
// Usage in package
Package::method();
// Testing facade
Package::shouldReceive('method')
->once()
->with('argument')
->andReturn('result');
// Macroable facade
Package::macro('customMethod', function ($value) {
return $this->getFacadeRoot()->method($value);
});
// Facade root access
$instance = Package::getFacadeRoot();
Answer: Publishing package configuration files:
// In service provider boot method
public function boot()
{
// Publish single config file
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php'),
], 'config');
// Publish multiple files
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php'),
__DIR__.'/../config/another.php' => config_path('another.php'),
], 'package-config');
// Publish assets
$this->publishes([
__DIR__.'/../assets' => public_path('vendor/package'),
], 'public');
// Publish views
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/package'),
], 'views');
// Publish migrations
$this->publishes([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'migrations');
}
// Publishing commands
php artisan vendor:publish --tag=config
php artisan vendor:publish --provider="Vendor\Package\PackageServiceProvider"
php artisan vendor:publish --all
// Conditional publishing
if ($this->app->runningInConsole()) {
$this->publishes([
// ...
], 'config');
}
Answer: Including routes and views in packages:
// Package routes
// In service provider
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/routes/web.php');
}
// routes/web.php in package
Route::group([
'prefix' => 'package',
'middleware' => ['web'],
'namespace' => 'Vendor\Package\Http\Controllers',
], function () {
Route::get('/dashboard', 'DashboardController@index');
});
// Package views
// In service provider
public function boot()
{
$this->loadViewsFrom(__DIR__.'/resources/views', 'package');
}
// Using package views
return view('package::dashboard', ['data' => $data]);
// Overriding package views
// Create in application: resources/views/vendor/package/dashboard.blade.php
// Publishing views
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/package'),
], 'views');
// View composers for package views
View::composer('package::dashboard', function ($view) {
$view->with('count', 100);
});
Mastering these advanced Laravel concepts demonstrates deep understanding of the framework and prepares you for senior-level positions. Remember that while knowing the answers is important, understanding the underlying principles and when to apply each concept is what truly sets expert developers apart.