name
Route::get('user/profile', function () {
//
})->name('profile');
Route::get('user/profile', [UserProfileController::class, 'show'])->name('profile');
// Generating URLs…
$url = route('profile');
// … for routes with parameters
$url = route('profile', ['id' => 1]);
// Generating Redirects…
return redirect()->route('profile');
resources/views/components
resources/views/components/alert.blade.php
resources/views/components/forms/input.blade.php
php artisan make:component forms.input --view
resources/views/components/alert.blade.php
<div class="alert alert-{{ $type }}">
<strong>{{ $title }}</strong>
{{ $slot }}
</div>
<x-alert type="danger" title="Error">
Something went wrong!
</x-alert>
: prefix to bind PHP expressions
<x-alert :type="$severity" :message="$errorMsg" />
<div class="alert" {{ $attributes }}>
{{ $slot }}
</div>
<!-- Usage -->
<x-alert id="main-alert" data-toggle="alert">Alert content</x-alert>
resources/views/components/card.blade.php
<div class="card">
<div class="card-header">
{{ $header }}
</div>
<div class="card-body">
{{ $slot }}
</div>
<div class="card-footer">
{{ $footer }}
</div>
</div>
<x-card>
<x-slot:header>Card Title</x-slot>
Main card content here
<x-slot:footer>Card Footer</x-slot>
</x-card>
php artisan make:component Alert
⟶ creates a view template in resources/views/components
app/View/Components
class Alert extends Component
{
public function __construct()
{
//
}
public function render()
{
return view('components.alert');
}
}
php artisan make:component Forms/Input
<x-alert/>
<x-forms.input/>
:) …
<x-alert type="error" :message="$message"/>
class Alert extends Component
{
public $type;
public $message;
public function __construct($type, $message)
{
$this->type = $type;
$this->message = $message;
}
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>/resources/views/components/alert.blade.php
<span class="alert-title">{{ $title }}</span>
<div class="alert alert-danger">
{{ $slot }}
</div>
<x-alert>
<x-slot:title>
Server Error
</x-slot>
<strong>Whoops!</strong> Something went wrong!
</x-alert>resources/views/components/forms/text-input.blade.php
<div class="form-group">
<label for="{{ $name }}" class="form-label">
{{ $label }}
</label>
<input type="text"
id="{{ $name }}"
name="{{ $name }}"
value="{{ old($name, $value ?? '') }}"
class="form-control @error($name) is-invalid @enderror"
{{ $attributes }}>
@error($name)
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<form action="{{ route('products.store') }}" method="POST">
@csrf
<x-forms.text-input
name="product_name"
label="Product Name"
required>
</x-forms.text-input>
<x-forms.text-input
name="email"
label="Email Address"
type="email"
placeholder="user@example.com">
</x-forms.text-input>
<button type="submit">Create</button>
</form>
\Illuminate\Session\Middleware\StartSession backs a permanent Session, only for the routes in routes/web.php
appname_session) with a unique ID on the client and store data on the server across multiple requests for the userdriver configuration in config/session.phpdatabase which requires a sessions table installable through 0001_01_01_000000_create_users_table.phpfile which means session data is stored as JSON in storage/framework/sessions$value = $request->session()->get('key');
$value = $request->session()->get('key', 'default');
$allData = $request->session()->all();
$request->session()->put('key', 'value');
$request->session()->flash('status', 'Task was successful!');
$request->session()->reflash(); // flash all data once again!
$request->session()->keep(['username', 'email']); // the same for a selection of vars
$request->session()->now('status', 'Task was successful!'); // store as flashed data but only for the current request
$request->session()->regenerate();
(often done after successful authentication in order to prevent session fixation)
regenerate() + flush() by $request->session()->invalidate();// Display a form to create a blog post...
Route::get('blogposts/create', 'PostController@create');
// Store a new blog post...
Route::post('blogposts/create', 'PostController@store');
// Display a form to create a blog post...
Route::get('blogposts/create', 'PostController@create');
// Store a new blog post...
Route::post('blogposts/create', 'PostController@store');
A CSRF attack forces a logged-on victim’s browser to send a forged HTTP request, including the victim’s session cookie and any other automatically included authentication information, to a vulnerable web application. This allows the attacker to force the victim’s browser to generate requests the vulnerable application thinks are legitimate requests from the victim.
<form action="https://your-application.com/user/email" method="POST">
<input type="email" value="malicious-email@example.com">
</form>
<script>
document.forms[0].submit();
</script>
// Vanilla PHP
<?php echo csrf_field(); ?>
// Blade Template Syntax …
@csrf
// … is the equivalent of:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
VerifyCsrfToken middleware (part of the web group) is already activated and does the checking
@include('common.errors') {{-- visualizes the $errors array --}}
<form method="POST" action="{{ url('blogposts/create') }}" accept-charset="UTF-8">
@csrf
<div class="form-group">
<label for="title">Title:</label>
<input class="form-control" name="title" type="text"
value="{{ old('title', '') }}" id="title">
</div>
<div class="form-group">
<label for="content">Content:</label>
<textarea class="form-control" rows="10" name="content" cols="50" id="content">
{{ old('content', '') }}
</textarea>
</div>
<input class="btn btn-primary pull-right" type="submit" value="submit">
</form>
public function create(): View
{
return view('blogpost.add');
}
public function store(Request $request): RedirectResponse
{
// building oldskool form errors …
$errors = array();
if (!(($request->has('title')) && (strlen($request->title) > 0))) {
$errors[] = 'The title field is required.';
} else if (strlen($request->title) > 125) {
$errors[] = 'The title may not be greater than 125 characters.';
} else if (Blogpost::where('title', $request->title)->exists()) {
$errors[] = 'The title has already been taken.';
}
if (!(($request->has('content')) && (strlen($request->content) > 0))) {
$errors[] = 'The content field is required.';
}
…
public function store(Request $request): RedirectResponse
{
// building oldskool form errors …
…
if (count($errors) > 0) {
// flash the form errors in the session
$request->session()->flash('errors', $errors);
// flash the request inputs in the session and take previous route
$request->flash();
return back();
// NOTE: flashing the request inputs was necessary since the user is REDIRECTED
// also possible: return back()->withInput();
} else {
Blogpost::create($request->all()); // mass assignment
return redirect('blogposts/success');
}
}
validate method
public function store(Request $request): RedirectResponse
{
$request->validate([
'title' => 'required|unique:blogposts|max:125',
'content' => 'required',
]);
// The blog post is valid, store in database...
}
ShareErrorsFromSession middleware (part of the web group) assures an $errors variable is available in your views
'start_date' => 'required|date|after:tomorrow',
'finish_date' => 'required|date|after:start_date',
'finish_date' => ['required', 'date', 'after:start_date'], // same, alternative notation
'photo' => 'image|mimes:jpeg,png', // is an image of any type and its content is jpeg or png
'avatar' => 'dimensions:min_width=100,min_height=200', // image with specified dimensions
'state' => 'exists:states,abbreviation', // exists in column abbreviation of table states
'email' => 'email:rfc,dns', // has email format and valid domain (requires intl package)
'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->uncompromised()],
// for optional fields !!!!!!
// since we have TrimStrings and ConvertEmptyStringsToNull middleware running
'publish_at' => 'nullable|date'
public function create(): View
{
return view('blogpost.add');
}
public function store(Request $request): RedirectResponse
{
$request->validate([
'title' => 'required|unique:blogposts|max:125',
'content' => 'required'
]);
Blogpost::create($request->all());
return redirect('blogposts/success');
}
validate() returns an associative array of the validated data. It does not contain non-validated fields and is considered an extra barrier against mass assignment vulnerabilities.
$validatedData = $request->validate([
'title' => 'required|unique:blogposts|max:125',
'content' => 'required'
]);
// in stead of $request->title we can now use $validatedData['title']
Blogpost::create($validatedData);
common/errors.blade.php such that it visualizes an $errors MessageBag
@if ($errors->any())
<!-- Form Error List -->
<div class="alert alert-danger">
<strong>Whoops! Something went wrong!</strong>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
Illuminate\Support\MessageBag also supports field-based retrieval, and has a specific Blade directive:
<input id="title" type="text" class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
asset())config/filesystems.php
local driver interacts with the local file system.
Storage::get('file.jpg'); operates on the default disk, while
Storage::disk('public')->get('file.jpg'); operates on the public disk.
local disk
local driver and its root is storage/app/privatepublic disk
local driver and its root is storage/app/publicstorage/app/public is neither located inside the web server's document root but …public/storage to storage/app/public ☺
$ php artisan storage:link
The [public/storage] directory has been linked.
asset() helper:
{{ asset('storage/file.txt') }}
public disk as the default filesystem disk in .env (and/or config/filesystems.php)storage/app/public<form> element (Views): check your enctype attribute (see
this course) * with multiple file uploads e.g. documents.*
image is a rule too
store() will assign a unique ID filename. Most of the time, it's a good practice. (think of duplicates)
WHERE (brand_id = 5 OR brand_id = 6) AND (price BETWEEN 100 AND 200) AND …
GET params
Session params
Route::get('/products', [ProductController::class, 'filter']);
parameter example: products?brand_id=64&category_id=3&size=L,XLold())
request() (or session()->get())
<select name="brand_id" id="brand_id" class="form-control">
@foreach ($brands as $brand)
<option value="{{ $brand->id }}" @if (request('brand_id', '') == $brand->id) selected="selected" @endif>{{ $brand->name }}</option>
@endforeach
</select>
$validator = Validator::make($request->all(), [ // Manually Creating Validators
'brand_id' => 'nullable|exists:brands,id',
…
]);
if ($validator->passes()) {
$products = Product::query(); // returning a neutral Builder object
if ($request->filled('brand_id')) { // might be some other condition e.g. has(), == 0
$products->where('brand_id', $request->brand_id); // chaining changes the state of the Builder object anyway !
}
$products->when($request->filled('category_id'), function (Builder $query, string $categoryId) { // syntactic sugar using when()
return $query->where('category_id', $categoryId);
});
// Never do: $products->where('size', 'L')->orWhere('size', 'XL')
// use logical grouping in stead
$products->where(function (Builder $query) {
$query->where('size', 'L')
->orWhere('size', 'XL')
});
// or more simple: whereIn
$products->whereIn('color', ['red', 'blue']);
}
Never do $products->where('size', 'L')->orWhere('size', 'XL')
... WHERE ((brand_id = 64) AND (category_id = 3) AND (size = 'L')) OR (size = 'XL')




$numPages = ceil($numItems / $numItemsPerPage);
paginate method with Query Builder or Eloquent
?page query string argument on the HTTP request
public function index(): View // controller method
{
$users = User::where('votes', '>', 100)->paginate(15);
// uncomment next line to change the paginator's target URL
// $users->setPath('custom/url');
return view('user.index', ['users' => $users]);
}
simplePaginate: it will only generate prev/next buttons<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
{{ $users->links() }}
FormRequest extends Request
php artisan make:request StorePostRequest
app/Http/Requests
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
*/
public function rules(): array
{
return [
//
];
}
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
*/
public function rules(): array
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
/**
* Determine if the user is authorized to make this request.
*
*/
public function authorize(): bool
{
// assuming my 'users' table has an enum column 'role'
return $this->user()->role == 'blogger';
}
return true; /**
* Store a new blog post.
*
*/
public function store(StorePostRequest $request): RedirectResponse
{
// The incoming request is valid and authorized ...
Blogpost::create($request->all());
// Extra FormRequest functionality:
// $request->validated()
// $request->safe()->only(['name', 'email'])
return redirect('/posts');
}
FormRequest extends Requestrules() fail, the errors and input values are flashed into the session and the user is redirected authorize() fails, a 403 page is shown