Laravel Introduction

Introduction to Laravel

Creating a new Laravel application

To create a new Laravel application:

composer create-project --prefer-dist laravel/laravel app_name

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

Running a Laravel application

To run an application in the local development server using the default port (8000):

php artisan serve

To run an application using a different port from the default port (8000), like when running multiple applications:

php artisan serve --port=8002

We 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:list

List 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 tinker

Add 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 UserFactory

After 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:seed

To 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:publish

Then 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 Job

In 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 TaskPosted

This 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 dev

Bundling 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

 

title

Resources

Books

  1. Laravel up and running

Websites

  1. onrep.dev

Jobs

  1. larajobs