Laravel Introduction
Introduction to Laravel
Creating a new Laravel application
To create a new Laravel application:
composer create-project --prefer-dist laravel/laravel app_nameTo install Laravel breeze(simple implementation of all of Laravel's authentication features: login, registration, password reset, email verification and password confirmation):
composer require laravel/breeze --dev
php artisan breeze:install bladeRunning a Laravel application
To run an application in the local development server using the default port (8000):
php artisan serveTo run an application using a different port from the default port (8000), like when running multiple applications:
php artisan serve --port=8002We also need to run the Vite development server:
npm run dev
Laravel Routes
Wildcards
When specifying the wild cards in routes, the default wild card is usually the id.
We can change this however using a colon to specify the column we want to use.
Route::get('/blogs/{blog:slug}', [BlogController::class, ‘show’])→name('blogs.show');Returning Views
Some routes just return a view without handling anything complex.
Instead of using:
Route::get('/about', function() {
return view('about');
}We can simplify this and use a short hand. The first argument is the URI and the second is the view we wish to return.
Route::view('/about', ‘about’);Grouping Routes
Assume we a situation where we repeat the same Controller for various routes.
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
Route::get('/products/create', [ProductController::class, 'create'])->name('products.create');
Route::get('/products/{product}', [ProductController::class, 'show'])->name('products.show');
Route::get('/products/{product}/{edit}', [ProductController::class, 'edit'])->name('products.edit');
Route::patch('/products/{product}', [ProductController::class, 'update'])->name('products.update');
Route::delete('/products/{product}', [ProductController::class, 'destroy'])->name('products.destroy');We can simplify this by grouping these routes.
Route::controller(JobController::class)→group(function () {
Route::get('/products', 'index')->name('products.index');
Route::get('/products/create', 'create')->name('products.create');
Route::get('/products/{product}', 'show')->name('products.show');
Route::get('/products/{product}/{edit}', 'edit')->name('products.edit');
Route::patch('/products/{product}', 'update')->name('products.update');
Route::delete('/products/{product}', 'destroy')->name('products.destroy');
});Another way is to use the route resource which registers all the routes for a typical restful restful controller.
Route::resource('products', ProductController::class);The route resource registers by default, 7 routes. But at times, we might only need a few. For that, we can either use only or except to specify the routes we need.
Only generates the routes we need.
Route::resource('products', ProductController::class, ['only' => ‘index’, 'show']);Except generates all other default routes except the ones we don't need.
Route::resource('products', ProductController::class, ['except' => ‘show’]);Listing Routes
List all the routes you have in your project:
php artisan route:listList routes excluding those from vendor:
php artisan route:list --except-vendor
Laravel Components
For example, to create a component for a button for adding new items:
First create a component, resources/views/components/add-button.blade.php
<a {{ $attributes→merge(['class' => ‘btn_link’]) }}>{{ $slot }}</a>We would then use this in a view as:
<x-add-button href="{{ route('tasks.create') }}">New Job</x-add-button>
Models
Properties
Table
If the table does not necessarily match with the Model name, we can explicitly define the table name.
protected $table = ‘table_name’;Fillable
This property is used to define which fields are safe to be filled mass assigned.
protected $fillable = [
'title',
'slug',
];Guarded
We can also define which fields are not to be mass assigned.
To make all fields mass assignable, we leave the guarded array blank.
protected $guarded = [];To only list some fields as not mass assignable:
protected $guarded = [
'title',
'slug',
];Timestamps
To disable timestamps so that the tables won't need to have created_at and updated_at fields:
public $timestamps = false;Relationships
Many to One
This means that many instances of the current model (the model that contains the belongsTo method) are associated with one instance of the related model.
public function relationship_name()
{
return $this->belongsTo(Parent_Model::class, 'foreignkey_field');
}One to Many
This means that one instance of the current model is associated with many instances of the related model.
public function relationship_name()
{
return $this->hasMany(Parent_model::class, 'foreignkey_field');
}Many to Many
This is mostly used in pivot tables.
public function relationship_name()
{
return $this->belongsToMany(Parent_model::class);
}This assumes you only have two fields for the foreign keys of the related tables.
To specify extra fields such as ordering:
public function relationship_name()
{
return $this->belongsToMany(Parent_model::class)->withPivot(['ordering']);
}We can also specify we want to order the items using the ordering field.
public function relationship_name()
{
return $this->belongsToMany(Parent_model::class)->withPivot(['ordering'])->orderBy('ordering');
}
Migrations
migrations
Eloquent
Tinker
To use tinker, run:
php artisan tinkerAdd records to a table:
App\Models\Users::create(['first_name' => ‘Alex’, ‘last_name’ => ‘Aaqil’]);View all records:
App\Models\Users::all();Find a single record (with a parameter such as the id or any other field you want to use to find your record):
$job = App\Models\Users::find(7);To delete:
$job→delete();
Model Factory
We use a model factory to quickly scaffold example data.
Create a factory
php artisan make:factory UserFactoryAfter creating a factory, we can use tinker to run the factory. For example to create 100 fake users:
App\Models\User::factory(100)→create();
Database Seeders
Working with Seeders
Creating a database seeder
To create a database seeder, edit the public function run for the seeder. For example, to create a users seeder
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use Hash;
class UsersSeeder extends Seeder
{
public function run(): void
{
$users = [
[
"first_name" => "Admin",
"last_name" => "Administrator",
"email" => "admin@gmail.com",
"phone_number" => "254746055487",
"password"=> Hash::make("@dmin"),
"user_level" => 2,
],
[
"first_name" => "User",
"last_name" => "Test",
"email" => "user@gmail.com",
"phone_number" => "254746055487",
"password"=> Hash::make("p@ssword"),
],
];
foreach($users as $user) {
User::create($user);
}
}
}Calling a database seeder
For seeders to be run, they need to be called in the main database seeder - database/seeders/DatabaseSeeder.php
public function run(): void
{
$this->call(AdminSeeder::class);
}Seeding a database
To seed a database by running all the seeders, we use the command
php artisan db:seedTo run a single seeder:
php artisan db:seed --class=<seeder_class_name>
Eager Loading
N+1 Problem
We eager loading to avoid the n+1 problem caused by lazy loading.
$results = Model::with('related_records')→get()instead of just lazy loading everything which runs a foreach loop. This means that if we have 50 records, we'll be running 50 queries.
$results = Model::all();We can also totally disable lazy loading in our Laravel application by going to app/Providers/AppServiceProvider.php and in then in the boot method, disable lazy loading. This would force us to use eager loading instead.
public function boot(): void
{
Model::preventLazyLoading();
}
Pagination
Pagination Types
When we have thousands of jobs, it wouldn't make sense to collect everything from the database using the get method, we would instead use paginate to collect just the number we need per page.
Paginate
Displays the page numbers as well as the total results.
$results = Model::with('related_records')→paginate(50);Simple paginate
We can however use simple paginate method so that we just have the Prev and Next buttons, which is more memory efficient.
$results = Model::with('related_records')→simplePaginate(50);Cursor Paginate
Uses some form of encoding for the range of records to show.
It's good for instances where you don't really need the URL to necessarily show the page you're on.
$results = Model::with('related_records')→cursorPaginate(50);Customizing Pagination
After using pagination in the model or controller, we have to enable pagination in our views.
In Laravel however, Tailwind CSS is used to style the pagination links, so if you're not using Tailwind CSS, you have to manually style the pagination links.
<div class="pagination">
{{ $results→links() }}
</div>To manually set up the pagination:
First run
php artisan vendor:publishThen select pagination so that the pagination files will be copied to the resources/views/vendor directory.
Now you can make adjustments to the tailwind.blade.php file.
Controller Classes
To create a controller class:
php artisan make:controller <Controller_name>To create a controller class with CRUD methods:
php artisan make:controller <Controller_name> -resource
Laravel Validation
Using Regex for validation
Assuming we want our phone numbers to start with 2547 or 2541 then be followed by 6 digits to make it a total of 10 digits:
$validated = $request->validate([
'phone_number' => [
'required',
'string',
'regex:/^(2547|2541)[0-9]{6}$/',
],
], [
'phone_number.regex' => 'The phone number must start with 2547 or 2541 and have exactly 10 digits. (254746055xxx or 25411055xxx)',
]);If we want to instead start with 0 then followed by 9 digits to make it a total of 10 digits:
'phone_number' => [
'required',
'string',
'regex:/^0[0-9]{9}$/',
],
Laravel CRUD
CRUD Operations
Update
View:
Since browsers only natively support get and post methods, we have to explicitly state in our forms that we are performing an update and not a store operation.
To do this we just a blade directive @method('PATCH')
<form method="post" action="{{ route('tasks.update', $task->id) }}">
@csrf
@method('PATCH')
...
</form>Route:
Route::patch('/tasks/{id}', [TaskController::class, ‘update’])→name('tasks.update');Destroy
View:
Just like in the Update operation, we also have to explicitly state in our forms that we are performing a delete operation.
To do this we just a blade directive @method('DELETE')
<form method="post" action="{{ route('tasks.destroy', $task->id) }}">
@csrf
@method('DELETE')
...
</form>Route:
Route::delete('/tasks/{id}', [TaskController::class, ‘destroy’])→name('tasks.destroy');
Laravel Middleware
Middleware
Inline Middleware
To apply a single inline middleware to a route:
Route::post('/tasks', [TaskController::class, ‘store’])->middleware('auth');To apply multiple inline middleware to a route:
Route::post('/tasks', [TaskController::class, ‘store’])->middleware(['auth', 'verified', 'admin']);Policies
Policies are like dedicated gates that allow you to authorize certain actions like delete or edit for your model, but unlike gates, they are more recommended for larger applications.
To create a policy, we provide the first argument as the name of the policy and then the model it should be associated with.
This creates a file: app/Policies/TaskPolicy.php
php artisan make:policy TaskPolicy JobIn the TaskPolicy.php file we can then define the method and the validations.
class JobPolicy
{
public function edit(User $user, Task $job): bool
{
return $task->user->is($user);
}
}We can then add this to our routes:
Route::post('/tasks', [TaskController::class, ‘store’])
->middleware(['auth', 'verified', 'admin'])
->can('edit', 'job');
Laravel Mails
To create a mailable, we use:
php artisan make:mail TaskPostedThis creates a file app/Mail/TaskPosted.php
In this file we can then edit the envelope and content functions.
public function __construct(public Job $job)
{
//
}
public function envelope(): Envelope
{
return new Envelope(
subject: 'Task Posted',
from: 'admin@aaqiluniversity.com',
replyTo: 'admin@aaqiluniversity.com'
);
}
public function content(): Content
{
return new Content(
view: 'mail.task-posted',
);
}Testing
To test the output of the email, we can create a dummy route:
Route::get('test', function() {
return new \App\Mail\TaskPosted();
});To send the mail:
Route::get('test', function() {
\Illuminate\Support\Facades\Mail::to('aaqiluniversity@gmail.com')->send(
new \App\Mail\TaskPosted()
);
return 'Done';
});Since there's not SMTP server that has been set up, the results of this will be placed in the storage/logs/laravel.log file.
Implementing a mailable
To implement a mailable in a controller, say store method for tasks:
public function store()
{
request()->validate([
'title' => ['required', 'min:3'],
'salary' => ['required']
)];
$task = Task::create([
'title' => request('title'),
'salary' => request('salary'),
'employer_id' => 1
]);
Mail::to($task->user->email)->send(
new \App\Mail\JobPosted($job)
);
return redirect('/jobs');
}Mailtrap
We can use mailtrap for a more realistic test of how the email will look like after it has been sent.
- create an account on mailtrap.io
- Email Testing > Inboxes > Start Testing
- Add Inbox then open the inbox
- Integrations > Laravel 9+
- Copy the configurations to your .env file
The .env file should now have the configurations:
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=****
MAIL_PASSWORD=****
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="info@aaqiluniversity.com"
MAIL_FROM_NAME="Aaqil University"
Laravel Queues
The objective is to understand concepts such as Queue, Job, Worker.
Queues help us run some actions in the background so that the user doesn't have to wait for a certain action that's not all that relevant to them.
Run the jobs in your queue:
php artisan queue:work
Laravel Vite
Vite is a bundler for CSS, images and JavaScript.
Configuring Vite
We use the file vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/styles.css',
'resources/js/app.js',
],
refresh: true,
}),
],
});The input array holds the files we want to bundle.
Using Vite CSS
To use to the bundled CSS from Vite, we add the Vite directive to our blade files:
<head>
@vite(['resources/css/app.css'])
</head>Hot Reloading
This refers to automatically reloading your pages after making a change to your CSS or JavaScript.
Once the Vite directive has been included, you should be able to see changes soon as you update your codebase.
npm run devBundling Assets
To bundle our assets, we just use the command:
npm run build
TDD
Understand Feature and Unit tests.
Arrange, Act, Assert
To run a test:
php run test