Middleware Security/Protection

Open up app/Http/Kernel.php and find protected $routeMiddleware

Underneath this you’ll see the different shorthands of calling the classes like:

'auth' => \App\Http\Middleware\Authenticate::class,

Which you can find under app/Http/Middleware/Authenticate.php

Now we can create our own middleware with this command:

php artisan make:middleware RoleMiddleware

Open up app/Http/Middleware/RoleMiddleware.php

Registering a new middleware and using it

To put in maintenance mode type:

php artisan down

Then put back up

php artisan up

Now register the RoleMiddleware in Kernel.php.. add to end of this:

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'role' => \App\Http\Middleware\RoleMiddleware::class,
    ];

Create a route for the middleware that’s basic like:

Route::get('/admin/user/roles', ['middleware'=>'role', function () {
    return "Middleware role";
}]);

Now update RoleMiddleware to handle this request to the middleware:

    public function handle(Request $request, Closure $next)
    {
        return redirect('/');
        //return $next($request);
    }

Now going to the /admin/user/roles URL will simply redirect the user the homepage.

Middleware – roles, migration and relations setup

Create a Role model to store the role of user:

php artisan make:model Role -m

Open up user migration and add this:

$table->integer('role_id');

In new role migration add:

$table->string('name');

Then

php artisan migrate:refresh

Open Role model:

    protected $fillable = [
        'name',
    ];

Open User model:

    public function role(){
        return $this->belongsTo('App\Models\Role');
    }

Custom Method

Method 1

Create another middleware:

php artisan make:middleware IsAdmin

Now open up Kernel to add the middleware:

'isAdmin' => \App\Http\Middleware\IsAdmin::class,

Now we’ll need to create roles in our database to use with this case. Add admin and subscriber. Then register a user on the web part at /register.

Create a relationship in User model called isAdmin

    public function isAdmin(){
        if($this->role->name == 'admin' ){
            return true;
        }
        return false;
    }

Now add a route to check whether your current logged in user is administrator (check database to see if it is first!)

Route::get('/', function () {
    $user = Auth::user();
    if($user->isAdmin()){
        echo 'this user is an administrator';
    }
    //return view('welcome');
});

Now change the user role to see if it works when not an administrator as well.

Method 2

Return homepage route back to original:

Route::get('/', function () {
    return view('welcome');
});

Add a redirect instead to the middleware when user role is administrator for IsAdmin:

use Illuminate\Support\Facades\Auth;
    public function handle(Request $request, Closure $next)
    {
        $user = Auth::user();
        if(!$user->isAdmin()){
            return redirect('/');
        }
        return $next($request);
    }

Next create a route that calls a new Controller:

Route::get('/admin', 'AdminController@index');

Create controller

php artisan make:controller AdminController

Add to AdminController:

    public function __construct(){
        $this->middleware('isAdmin');
    }
    public function index(){
        return "you are an administrator because you are seeing this page";
    }

Now if you are an admin and visit /admin you’ll see the above message. If you aren’t, you’ll be redirected to the homepage.

Forms Login

Creating the login system

In Laravel 8 things have changed for the authentication system. 

First you need use your composer :

composer require laravel/jetstream

Then you have to create a new project:

laravel new login --jet

Which jetstream package do you prefer 0 livewire 1 inertia. Choose livewire here. Inertia when you want to use React.

Prepare database in phpmyadmin and make migrate:

php artisan migrate

Now, you can go to the registration page:

http://127.0.0.1:8000/register

Register a user and also try logging out and in again.

Login creation overview

Check the default route list it created:

php artisan route:list

Open up your controller folder to view all the files created. Then check your database to see the user created and the remember me token created for them.

Retrieving authenticated user data

Update routes to see new message on homepage:

Route::get('/', function () {
    if(Auth::check()){
        return 'the user is logged in';
    }
    //return view('welcome');
});

And this is attempting to go back to page where you tried to access but redirects to login page:

Route::get('/', function () {
    // if(Auth::check()){
    //     return 'the user is logged in';
    // }
    //return view('welcome');

    if(Auth::attempt(['username'=>$username,'password'=>$password])){
        return redirect()->intended();
    }
});

Forms Uploading Files

Modifying our create view for file input

{!! Form::open(['method'=>'POST','route'=>'posts.store','files'=>true]) !!}
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::file('file',['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Create Post',['class'=>'btn btn-primary']) !!}
    </div>
{!! Form::close() !!}

You are adding a files = true to the form tag and then adding a file field.

Retrieving File Data

Update store function to just return the file name and comment out validation from CreatePostRequest

    public function store(CreatePostRequest $request)
    {
        $file = $request->file('file');
        echo $file;
        echo '<br>';
        echo $file->getClientOriginalName();

        echo '<br>';
        echo $file->getClientSize();
    }

The first shows the temporary file name and the second shows the file name from your computer.

Persisting File Data into the Database

Create a migration to store the file info to database:

php artisan make:migration add_path_column_to_posts --table=posts

Open up and add these to up and down functions:

$table->string('path');
$table->dropColumn('path');

Then migrate:

php artisan migrate

Update your Post model to allow path column for mass migration:

    protected $fillable = [
        'title',
        'content',
        'user_id',
        'path'
    ];

Now edit your store function in PostsController:

    public function store(CreatePostRequest $request)
    {
        $input = $request->all();
        if($file = $request->file('file')){
            $name = $file->getClientOriginalName();
            $file->move('images',$name);
            $input['path'] = $name;
        }
        Post::create($input);
    }

Also if you check your public folder, you’ll see an images folder has been created with the images name.

Displaying images and using accessors to make it easy

Update your views post index page:

<ul>
    @foreach($posts as $post)
        <div class="image-container">
            <img height="100" src="/images/{{$post->path}}" alt="" />
        </div>
        <li><a href="{{route('posts.show',$post->id)}}">{{$post->title}}</a></li>
    @endforeach
</ul>

Let’s create a simple accessor that puts the directory of the images in the Post model. In Post model:

    public $directory = "/images/";public $directory = "/images/";
    public function getPathAttribute($value){
        return $this->directory.$value;
    }

Now change index.blade.php to:

<ul>
    @foreach($posts as $post)
        <div class="image-container">
            <img height="100" src="{{$post->path}}" alt="" />
        </div>
        <li><a href="{{route('posts.show',$post->id)}}">{{$post->title}}</a></li>
    @endforeach
</ul>

Database Some More Model Manipulation

Laravel includes Carbon. But to add to another project using composer, do composer require nesbot/carbon.

Dates

use Carbon\Carbon;
Route::get('/dates', function(){
    $date = new DateTime('+1 week');
    echo $date->format('m-d-Y');
    echo '<br>';
    echo Carbon::now()->addDays(10)->diffForHumans();
    echo '<br>';
    echo Carbon::now()->subMonths(5)->diffForHumans();
    echo '<br>';
    echo Carbon::now()->yesterday();
});

Accessors

Route::get('/getname', function(){
    $user = User::find(1);
    echo $user->name;
});

Update your User model to manipulate the output anytime the user’s name is used. Format here is important, needs to start with get, then the attribute name and then the word attribute.

    public function getNameAttribute($value) {
        return strtoupper($value);
    }

Mutators

Mutators will manipulate the data before its sent to the database.

    public function setNameAttribute($value) {
        $this->attributes['name'] = strtolower($value);
    }

Add to route:

Route::get('/setname', function(){
    $user = User::find(1);
    $user->name = 'William';
    $user->save();
});

Now if you check the database after visiting setname, you should see william is in lowercase.

Query Scope

The usual way to query is to do something like this (open PostController):

    public function index()
    {
        $posts = Post::orderBy('id','desc')->get();
        return view('posts.index', compact('posts'));
    }

Instead we are going to make orderBy shorter by adding a function to our Post model for queries.

    public static function scopeMeek($query) {
        return $query->orderBy('id','desc')->get();
    }

Update PostController:

    public function index()
    {
        $posts = Post::meek();
        return view('posts.index', compact('posts'));
    }

Laravel Forms Package and Validation

Installing the package

You need to get the “laravel illuminate/html collective” which you can google.

First we’ll add it manually. Go here:

https://laravelcollective.com/docs/6.x/html

Edit your project’s composer.json file to require laravelcollective/html. Find your require block and add:

"laravelcollective/html": "6.*"

In terminal:

composer update

Now you need to add to any Model that’s going to use it. In this case add to the Post model:

use Collective\Html\Eloquent\FormAccessible;

Modifying the create form

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open() !!}
    <input type="text" name="title" placeholder="Enter title" />
    <input type="submit" name="submit" />
{!! Form::close() !!}

@endsection
Remove the token as it'll create it for you. If you inspect your page form, you should see the token is added automatically. However if you submit the form you'll get an error. Its sending to the wrong URL.

Okay make these edits:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open(['method'=>'POST','route'=>'posts.store']) !!}
    <input type="text" name="title" placeholder="Enter title" />
    <input type="submit" name="submit" />
{!! Form::close() !!}

@endsection

Next add fields using the package like this:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open(['method'=>'POST','route'=>'posts.store']) !!}
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Create Post',['class'=>'btn btn-primary']) !!}
    </div>
{!! Form::close() !!}
@endsection

Modifying the edit/delete form

@extends('layouts.app')

@section('content')
<h1>Edit Post</h1>
{!! Form::model($post,['method'=>'PATCH','route'=>['posts.update',$post->id]]) !!}
    {!! Form::label('title','Title:') !!}
    {!! Form::text('title',null,['class'=>'form-control']) !!}
    
    {!! Form::submit('Update Post',['class'=>'btn btn-info']) !!}
{!! Form::close() !!}

<!-- <form method="post" action="/posts/{{$post->id}}"> -->
{!! Form::open(['method'=>'DELETE','route'=>['posts.destroy',$post->id]]) !!}
    {!! Form::submit('Delete Post',['class'=>'btn btn-danger']) !!}
{!! Form::close() !!}

@endsection

Basic Validation

Update the store function in PostController like this:

    public function store(Request $request)
    {
        $this->validate($request,[
            'title'=>'required|max:4'
        ]);
        $post = new Post;
        $post->title = $request->title;
        $post->save();
        return redirect('/posts');
    }

Now the form won’t enter without a value.

Displaying Errors

The validate function will create an errors variable when it fails which can be used in the views. So in our create.blade.php add this part:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
{!! Form::open(['method'=>'POST','route'=>'posts.store']) !!}
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control']) !!}
    </div>
    <div class="form-group">
        {!! Form::submit('Create Post',['class'=>'btn btn-primary']) !!}
    </div>
{!! Form::close() !!}

@if(count($errors)>0)
    <div class="alert alert-danger">
        @foreach($errors->all() as $error)
        <li>{{$error}}
        @endforeach
    </div>
@endif
@endsection

You should see errors added to bottom of form now.

Advance validation

Instead of creating the validation inside the store function, we are going to create a validation class that we’ll call inside the store function.

To see all your functions available type:

php artisan

You’ll see make:request Create a new form request class which is what we want to do. So next type this command:

php artisan make:request CreatePostRequest

You’ll find this file under App\Http\Requests. Open it and change authorize function to return true since we aren’t creating something that needs it.

Update rules function like so:

    public function rules()
    {
        return [
            'title'=>'required|max:4',
        ];
    }

That’s it! Very simple. Now we need to use it in our PostsController.

use App\Http\Requests\CreatePostRequest;
    public function store(CreatePostRequest $request)
    {
        $post = new Post;
        $post->title = $request->title;
        $post->save();
        return redirect('/posts');
    }

Laravel Forms and Validation

Create 4 views under a new folder called “posts”

create.blade.php
edit.blade.php
index.blade.php
show.blade.php

To our create.blade.php:

@extends('layouts.app')

@section('content')
<h1>Create Post</h1>
<form method="post" action="/posts">
    @csrf <!-- {{ csrf_field() }} -->
    <input type="text" name="title" placeholder="Enter title" />
    <input type="submit" name="submit" />
</form>

@endsection
You need to match the name of the column in posts.. is it named title? If so then the input name is correct. To save this to the posts method you just need to use the action /posts. @csrf is needed to create a token so form will work.

Find the posts routes methods by typing this:

php artisan route:list

You’ll see that the name store deals with the method POST. So find the store method in your posts controller.. this will be the function that will deal with the data when the form is submitted.

Now to see your form find the create (GET) function and add to this function:

    public function create()
    {
        return view('posts.create');
    }

The dot notation gets converted to slash in the browser.. where you can view it at base.com/posts/create.

Now get the store function to show the data submitted from the form:

    public function store(Request $request)
    {
        //return $request->all();
        //return $request->get('title');
        return $request->title;
    }

Update this to save to the database in different ways and redirect to index page like this:

    public function store(Request $request)
    {
        Post::create($request->all());
        return redirect('/posts');
    }
    public function store(Request $request)
    {
        $input = $request->all();
        $input['title'] = 'yo';
        Post::create($input);
        return redirect('/posts');
    }
    public function store(Request $request)
    {
        $post = new Post;
        $post->title = $request->title;
        $post->save();
        return redirect('/posts');
    }

Index controller should now show the index view and pass the post info:

    public function index()
    {
        $posts = Post::all();
        return view('posts.index', compact('posts'));
    }

In your index view update to show all the posts information:

@extends('layouts.app')

@section('content')

<ul>
    @foreach($posts as $post)
        <li>{{$post->title}}</li>
    @endforeach
</ul>

@endsection

Now if you submit the form you’ll be redirected to the index view which will show all the posts in the database including the last one added.

To see just one post, let’s add this to our show function in PostController:

    public function show($id)
    {
        $post = Post::findOrFail($id);
        return view('posts.show',compact('post'));
    }

In your show view write this:

@extends('layouts.app')

@section('content')

<h1>{{$post->title}}</h1>

@endsection

Now if you visit url /posts/1, you’ll see the first post. Other numbers will show other posts. It will have to match the id in the database of course.

Update your index view to link to the show view like so:

@extends('layouts.app')

@section('content')

<ul>
    @foreach($posts as $post)
        <li><a href="{{route('posts.show',$post->id)}}">{{$post->title}}</a></li>
    @endforeach
</ul>

@endsection

Now let’s create our Edit controller and view.

The view:

@extends('layouts.app')

@section('content')
<h1>Edit Post</h1>
<form method="post" action="/posts/{{$post->id}}">
    <input type="hidden" name="_method" value="PUT" />
    @csrf <!-- {{ csrf_field() }} -->
    <input type="text" name="title" placeholder="Enter title" value="{{$post->title}}" />
    <input type="submit" name="submit" />
</form>

@endsection
The action needs to send the post ID and a hidden field is needed called PUT which sends it to the update function in the controller.

The controller:

    public function edit($id)
    {
        $post = Post::findOrFail($id);
        return view('posts.edit',compact('post'));
    }

Now edit the update function in the controller:

    public function update(Request $request, $id)
    {
        $post = Post::findOrFail($id);
        $post->update($request->all());
        return redirect('/posts');
    }

Now update the show view to have links to edit form:

@extends('layouts.app')

@section('content')

<h1><a href="{{route('posts.edit',$post->id)}}">{{$post->title}}</a></h1>

@endsection

Lastly we are going to use a form to delete some of this data.

Find the destroy function in PostContoller and add this:

    public function destroy($id)
    {
        $post = Post::whereId($id)->delete();
        return redirect('/posts');
    }

Since everything already links nicely.. just edit you edit view like this:

@extends('layouts.app')

@section('content')
<h1>Edit Post</h1>
<form method="post" action="/posts/{{$post->id}}">
    <input type="hidden" name="_method" value="PUT" />
    @csrf <!-- {{ csrf_field() }} -->
    <input type="text" name="title" placeholder="Enter title" value="{{$post->title}}" />
    <input type="submit" name="submit" value="UPDATE" />
</form>

<form method="post" action="/posts/{{$post->id}}">
    <input type="hidden" name="_method" value="DELETE" />
    @csrf <!-- {{ csrf_field() }} -->
    <input type="submit" name="submit" value="DELETE" />
</form>

@endsection

Notice that your method is now DELETE and we still pass the post ID through the form action tag.

Eloquent Polymorphic Many to Many Relationship CRUD

Create 4 models, Post, Video, Tag, and Taggable

php artisan make:model Post -m   
php artisan make:model Video -m
php artisan make:model Tag -m
php artisan make:model Taggable -m

In our migrations

create_posts_table, create_videos_table and create_tags_table.php:

            $table->string('name');

create_taggables_table:

            $table->integer('tag_id');
            $table->integer('taggable_id');
            $table->string('taggable_type');

Now you can migrate it:

php artisan migrate

Update Models

Tag:

    protected $fillable = [
        'name',
    ];

Taggable:

    protected $fillable = [
        'name',

    ];
    public function taggable(){
        return $this->morphTo();
    }

Post and Video:

    protected $fillable = [
        'name',
    ];
    public function tags(){
        $this->morphToMany('App\Models\Tag', 'taggable');
    }

Create a few staff members and products in the database.

Different CRUD statements to add to routes:

Route::get('/create', function () {
    $post = Post::create(['name'=>'My first post']);
    $tag1 = Tag::find(1);
    $post->tags()->save($tag1);
    $video = Video::create(['name'=>'video.mov']);
    $tag2 = Tag::find(2);
    $video->tags()->save($tag2);
});

Route::get('/read', function () {
    $post = Post::findOrFail(4);
    foreach($post->tags as $tag){
        echo $tag;
    }
});

Route::get('/update', function () {
    $post = Post::findOrFail(4);
    foreach($post->tags as $tag){
        return $tag->whereName('php')->update(['name'=>'updated php']);
    }
});

Route::get('/update2', function () {
    $post = Post::findOrFail(4);
    $tag = Tag::find(4);
    $post->tags()->save($tag);
});

Route::get('/attach', function () {
    $post = Post::findOrFail(4);
    $tag = Tag::find(2);
    $post->tags()->attach($tag);
});

Route::get('/sync', function () {
    $post = Post::findOrFail(4);
    $tag = Tag::find(2);
    $post->tags()->sync([1,2]);
});

Route::get('/delete', function () {
    $post = Post::findOrFail(4);
    foreach($post->tags as $tag){
        $tag->whereId(2)->delete();
    }
});

Install AIMEOS Laravel project

You’ll need to create an empty database with username and password. Create a password without special characters. You’ll be inputting this as part of the install. You’ll also create the first user.

Password fields show as empty.. so can be difficult to know if you've pasted in the password correctly. 

Create a new site called myshop:

php composer create-project aimeos/aimeos myshop

This will set up a demo site automatically for you.. so will finish off with the correct migrations.

Eloquent Polymorphic Relationship CRUD

Create 3 models, Staff, Product, and Photo

php artisan make:model Staff -m 
php artisan make:model Product -m 
php artisan make:model Photo -m
Here we are creating a pivot table which follows the some rules: alphabetical order of the two table names in singular case

In our migrations

create_staff_table:

            $table->string('name');

create_products_table:

            $table->string('name');

create_photos_table:

            $table->string('path');
            $table->integer('imageable_id');
            $table->string('imageable_type');

Now you can migrate it:

php artisan migrate

Update Models

Photo:

    protected $fillable = [
        'path',

    ];
    public function imageable(){
        return $this->morphTo();
    }

Product and Staff:

    protected $fillable = [
        'name',
    ];
    public function photos(){
        $this->morphMany('App\Models\Photo','imageable');
    }

Create a few staff members and products in the database.

Different CRUD statements to add to routes:

Route::get('/create', function () {
    $staff = Staff::findOrFail(1);
    $staff->photos()->create(['path'=>'example.jpg']);
});

Route::get('/read', function () {
    $staff = Staff::findOrFail(1);
    foreach($staff->photos as $photo){
        echo $photo->path;
    }
});

Route::get('/update', function () {
    $staff = Staff::findOrFail(1);
    $photo = $staff->photos()->whereId(1)->first();
    $photo->path = "new.jpg";
    $photo->save();
});

Route::get('/delete', function () {
    $staff = Staff::findOrFail(1);
    $staff->photos()->wherePath('new.jpg')->delete();
});

//Create photo without imageable_type, so no attachment
Route::get('/assign', function () {
    $staff = Staff::findOrFail(1);
    $photo = Photo::findOrFail(3);
    $staff->photos()->save($photo);
});

Route::get('/unassign', function () {
    $staff = Staff::findOrFail(1);
    $staff->photos()->whereId(3)->update(['imageable_id'=>0,'imageable_type'=>'']);
});

Eloquent Many to Many Relationship CRUD

We will need a new model and migrations for role and role_user

php artisan make:model Role -m
php artisan make:migration create_user_role_table --create=role_user
Here we are creating a pivot table which follows the some rules: alphabetical order of the two table names in singular case

In our migrations add this to role_user:

            $table->integer('user_id')->unsigned()->nullable()->index();
            $table->integer('role_id')->unsigned()->nullable()->index();

And this to role:

            $table->string('name');

Now you can migrate it:

php artisan migrate

Update Models with fillable content for Role and a roles method for User.

Role:

    protected $fillable = [
        'name',
    ];

User:

    public function roles(){
        return $this->belongsToMany('App\Models\Role');
    }

Different CRUD statements to add to routes:

Route::get('/create', function () {
    $user = User::findOrFail(1);
    $role = new Role(['name'=>'Administrator']);
    $user->roles()->save($role);
});

Route::get('/read', function () {
    $user = User::findOrFail(1);
    foreach($user->roles as $role){
        echo $role->name;
    }
});

Route::get('/update', function () {
    $user = User::findOrFail(1);
    if($user->has('roles')){
        foreach($user->roles as $role){
            if($role->name=="Administrator"){
                $role->name="subscriber";
                $role->save();
            }
        }
    }
});

Route::get('/delete', function () {
    $user = User::findOrFail(1);
    foreach($user->roles as $role){
        $role->whereId(3)->delete();
    }
});

Route::get('/attach', function () {
    $user = User::findOrFail(1);
    $user->roles()->attach(4);
});

Route::get('/detach', function () {
    $user = User::findOrFail(1);
    $user->roles()->detach(4);
});

Route::get('/sync', function () {
    $user = User::findOrFail(1);
    $user->roles()->sync([4,5,6]);
});
Attach won't check the database to see if it's already been attached.. so with this method you'll need to check your database.

Sync is a bit better because it won't add duplicates. If you don't add previous roles in the database to the sync method, then it will delete those old roles.