Project : ✅ Day 6 Completed
Laravel Breeze provides a minimal, simple authentication scaffolding using Blade and Tailwind CSS.
composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev
php artisan migrate- Login / Register UI
- Email verification
- Password reset
- Auth middleware
Handled role-based access using Spatie Laravel Permission.
composer require spatie/laravel-permission
php artisan vendor:publish --tag="permission-config"
php artisan vendor:publish --tag="permission-migrations"
php artisan migrateuse Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable {
use HasRoles;
}Role::create(['name' => 'admin']);
$user->assignRole('admin');Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/admin/dashboard', [DashboardController::class, 'admin']);
});- User Request → Browser sends HTTP request
- Entry Point →
public/index.php - HTTP Kernel loads middlewares
- Service Providers bootstrap Laravel
- Middleware Execution → Auth, CSRF, etc.
- Route Matching
- Controller Execution
- View Rendering
- Response Creation
- Terminate Middleware
- Response to Browser
Client Request → index.php → Kernel → Providers → Middleware → Route → Controller → Response → Browser
- User → hasMany(Post)
- Post → belongsTo(User, Category), belongsToMany(Tag)
- Category → hasMany(Post)
- Tag → belongsToMany(Post)
- UserFactory, CategoryFactory, TagFactory, PostFactory
Post::factory(20)->create()->each(function ($post) {
$post->tags()->attach(Tag::inRandomOrder()->take(rand(1, 3))->pluck('id'));
});Run using:
php artisan migrate:fresh --seedpublic function getFormattedTitleAttribute() {
return strtoupper($this->title);
}- Stored in
storage/app/public/posts - Cleaned filenames using
Str::slug - Old images deleted on update
# Events
php artisan make:event UserRegistered
php artisan make:event PostCreated
# Listeners (queued by default with --queued flag)
php artisan make:listener SendWelcomeEmailListener --event=UserRegistered --queued
php artisan make:listener SendPostPublishedNotification --event=PostCreated --queued
# Notification & Mail
php artisan make:notification PostPublishedNotification
php artisan make:mail WelcomeMail --markdown=emails.welcome- User registers →
UserRegisteredevent fires →SendWelcomeEmailListenerqueues aWelcomeMail. - User creates a post →
PostCreatedevent fires →SendPostPublishedNotificationlistener queues a notification to all users (mail + database).
All listeners implement ShouldQueue, so emails/notifications are processed asynchronously by your queue worker (php artisan queue:work).
Edit app/Providers/EventServiceProvider.php:
protected $listen = [
\App\Events\UserRegistered::class => [
\App\Listeners\SendWelcomeEmailListener::class,
],
\App\Events\PostCreated::class => [
\App\Listeners\SendPostPublishedNotification::class,
],
];Laravel now automatically discovers and dispatches the correct listener when each event is fired.
// app/Events/UserRegistered.php
class UserRegistered {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user) { $this->user = $user; }
}// app/Listeners/SendWelcomeEmailListener.php
class SendWelcomeEmailListener implements ShouldQueue {
public function handle(UserRegistered $event) {
Mail::to($event->user->email)->queue(new WelcomeMail($event->user));
}
}Trigger (after creating the user and logging them in):
event(new UserRegistered($user));// app/Events/PostCreated.php
class PostCreated {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $post;
public function __construct(Post $post) { $this->post = $post; }
}// app/Listeners/SendPostPublishedNotification.php
class SendPostPublishedNotification implements ShouldQueue {
public function handle(PostCreated $event) {
$users = User::all(); // Filter roles if needed
Notification::send($users, new PostPublishedNotification($event->post));
}
}Trigger in PostController@store after saving and tagging:
event(new PostCreated($post));class PostPublishedNotification extends Notification implements ShouldQueue {
public function via($notifiable) { return ['mail', 'database']; }
public function toMail($notifiable) {
return (new MailMessage)
->subject('A new post was published!')
->greeting('Hello!')
->line("Post: {$this->post->title}")
->action('View Post', route('posts.show', $this->post->slug))
->line('Thanks for reading!');
}
public function toDatabase($notifiable) {
return [ 'post_id' => $this->post->id, 'title' => $this->post->title ];
}
}The notification is stored in the notifications table and emailed—both handled in the queue.
class WelcomeMail extends Mailable
{
use Queueable, SerializesModels;
public $user;
/**
* Create a new message instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Welcome to Our Platform',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.welcome',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}Create resources/views/emails/welcome.blade.php with a friendly Markdown template.
- Dispatch events using
event(new UserRegistered($user))inside a test and assert that jobs were pushed:
event(new UserRegistered($user));Run a worker locally:
php artisan queue:workFor production visibility, consider adding Laravel Horizon for dashboard‑level monitoring of queued jobs.
| Task | Status |
|---|---|
| Events & Listeners scaffolded | ✅ |
Listeners queued (ShouldQueue) |
✅ |
| Welcome email on user registration | ✅ |
| Post‑published notification (mail+DB) | ✅ |
| Tested with Bus / Notification fakes | ✅ |
Your application now embraces event‑driven architecture, improves performance with queues, and enhances UX with real‑time notifications.
php artisan make:policy PostPolicy --model=Postpublic function update(User $user, Post $post) {
return $user->id === $post->user_id;
} Gate::define('view-premium', function (User $user) {
return $user->points > 100;
});class StatsService
{
public function getMonthlyStats(): array
{
return [
'new_users' => User::whereMonth('created_at', now()->month)->count(),
'posts_created' => Post::whereMonth('created_at', now()->month)->count(),
];
}
}php artisan make:provider StatsServiceProviderpublic function register(): void
{
$this->app->singleton(StatsService::class, function ($app) {
return new StatsService();
});
}composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateHandles register and login with token creation.
public function rules() {
return ['title' => 'required|string|max:255'];
}Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/profile', function (Request $request) {
return $request->user();
});
Route::apiResource('posts', PostController::class);
});return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'author' => $this->user?->name, // null-safe: if user exists
'category' => $this->category?->name, // optional
'created_at' => $this->created_at->diffForHumans(),
];- Created reusable trait for
actingAsUser - Tests: create, update, access control
DB_DATABASE=check_databasephp artisan make:test PostControllerTest
# path
tests/Feature/PostControllerTest.phpphp artisan test --filter=PostControllerTest
or
./vendor/bin/phpunit --filter=PostControllerTestphp artisan make:command CleanupOldSoftDeletedPostsDeletes posts older than 30 days:
Post::onlyTrashed()->where('deleted_at', '<', now()->subDays(30))->forceDelete();- Safe validation and storage
- Deletes old image on update
- Deletes image with data using destroy
php artisan make:migration add_image_to_posts_table --table=posts
Schema::table('posts', function (Blueprint $table) {
$table->string('image')->nullable(); // stores image filename/path
});
php artisan migrate
# in model
protected $fillable = ['title', 'content', 'image']; // include image
# In form
enctype="multipart/form-data"
# Create storage link
php artisan storage:link
#accesss in blade
@if ($post->image)
<img src="{{ asset('storage/' . $post->image) }}" width="300" alt="Post Image">
@endif#Install Scout
composer require laravel/scout
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Model uses:
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
// Optional: define which fields are searchable
public function toSearchableArray()
{
return [
'title' => $this->title,
'content' => $this->content,
];
}
}php artisan scout:import "App\Models\Post"Post::search($query)->paginate(10);- In model add
use Illuminate\Database\Eloquent\SoftDeletes;
use SoftDeletes;
#create column
php artisan make:migration add_deleted_at_to_posts_table --table=posts
$table->softDeletes(); // adds deleted_at column
php artisan migrate- Retrieve All Records
$posts = Post::withTrashed()->get();
# only Deleted Records
$posts = Post::onlyTrashed()->get();
- Restore Soft Deleted Record
Post::withTrashed()->find(1)->restore();
- Permanently delete
Post::withTrashed()->find(1)->forceDelete();