Laravel for Beginners #3

In this article, we are going to finish up the last two topics in Laravel, database relations and controllers, and before we can start building our blog application, let’s first use what we’ve learned so far to create a simple homepage.

Let’s start with database relations. In a typical application, the database tables aren’t always independent. For example, we could have a user who has posts, and posts that belong to a user. And Laravel offers us a way to define these relations using the Eloquent models.

Database relations

This section is probably a little difficult for beginners, but don’t worry, we are going to come back to this topic when we start building our blog app. Here, I only picked the ones we need to use to build a simple blogging system.

One to one

This is the most basic relation. For example, each User is associated with one Phone. To define this relationship, we need to place a phone method on the User model.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the phone associated with the user.
     */
    public function phone()
    {
        return $this->hasOne(Phone::class);
    }
}

This Phone model automatically assumes that there is a user_id column inside the phones table. This means you also need to define this column in the migration file. The user_id column stores the id of the user that owns this phone.

Now, what if we want to do the opposite? The inverse of “has one” would be “belongs to one”. For example, each Phone would belong to one User. In order to define the inverse of the one-to-one relationship. We place a user method on the Phone model.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Phone extends Model
{
    /**
     * Get the user that owns the phone.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

One to many

A one-to-many relationship is used to define relationships where a single model owns any amount of other models. For example, one Category could have many Posts. Just like one-to-one relation, it can be defined by putting a posts method in the Category model.

1
2
3
4
5
6
7
class Category extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

Here the Post model assumes the posts table has a category_id column.

However, sometimes we need to find the category through the post. The inverse of “has many” would be “belongs to”. In order to define the inverse of the one-to-many relationship. We place a category method on the Post model.

1
2
3
4
5
6
7
class Post extends Model
{
    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

Many to many

The many-to-many relation is a little more tricky. For example, we can have a User who has many roles, and a Role which has many users.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}

These two models assume there is a role_user table in the database. And the role_user table contains user_id and role_id columns. This way, we can match the user with the role and vice versa.

Controllers

Now that we’ve studied routes, views, models as well as database relations, it’s time to talk about the thing that connects them all together. Remember when we talked about routes, we saw an example like this?

1
2
3
Route::get('/number/{num}', function ($num) {
    return view('view', ['num' => $num]);
});

It looks fine right now, but when you have a huge project, putting all the logic in the route file will make it very messy. A better solution would be making sure that the route always points to a method in a controller, and we’ll put the logic inside that method.

1
2
3
use App\Http\Controllers\UserController;

Route::get('/users/{id}', [UserController::class, 'show']);

To create a new controller, use the following command:

1
php artisan make:controller UserController

Laravel’s controller files are stored under the directory app/Http/Controllers/.

Basic controller

Let’s take a look at an example. The show() method expects a variable $id, and it uses that id to locate the user in the database and returns that user to the view.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\User;

class UserController extends Controller
{
    /**
     * Show the profile for a given user.
     *
     * @param  int  $id
     * @return \Illuminate\View\View
     */
    public function show($id)
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}

Sometimes, you need to define a controller that only has one method. In this case, we can simplify the code by changing the method’s name to __invoke:

1
2
3
4
5
6
7
8
9
class UserController extends Controller
{
    public function __invoke($id)
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}
1
Route::get('/users/{id}', UserController::class);

Now we don’t have to specify the method name in the route.

Resource controller

The resource is another modern web design concept we need to talk about. Usually, we see all the Eloquent models as resources, meaning that we perform the same set of actions on all of them, create, read, update and delete (CRUD).

To create a resource controller, we can use the --resource option:

1
php artisan make:controller PhotoController --resource

The generated controller will automatically contain the following methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PhotoController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

The comments explain their corresponding function and purposes.

Laravel also offers us a very easy way to register routes for all of these methods.

1
Route::resource('photos', PhotoController::class);

The following routes with different HTTP methods will be automatically created.

HTTP Method URI Action Route Name
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

Creating a homepage

Now, we are finally going to start using everything we’ve learned about Laravel so far, and use it to create a real project. Let’s start with something easier. In this part, we are going to create a sample homepage, which only contains three variables, a name, a description and a logo. But through this example, I’m going to show you exactly how to retrieve these data from the database, how to create new data, and how to save them in a real web application.

If you’ve followed the previous tutorial, you should already know how to create a Laravel project. As a quick reminder, this is what we need to do:

1
2
3
4
5
6
7
composer global require laravel/installer

laravel new example-app

cd example-app

php artisan serve

Remember to configure the .env file and database like we’ve talked about before.

Database and models

Next, let’s start with the database. We’ll create a migration file for the home table. This table should contain the website’s name, description and logo. In the case of the logo, the database can’t really store files, so instead, it stores the path which points to the location where the file is stored.

Generate a migration file:

1
php artisan make:migration create_home_table
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateHomeTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('home', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('name');
            $table->string('description');
            $table->string('logo');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('home');
    }

}

In this example, we created six columns in the home table. id() creates an ID column, which is commonly used for indexing. timestamps() creates two columns, created_at and uptated_at. These two columns will be automatically updated when the record is created and updated. And finally, string() creates a column with type VARCHAR, whose default length is 255 bytes.

To apply the changes, use the following command:

1
php artisan migrate

To view the changes you’ve made, you can use software such as DB Browser for SQLite or PhpMyAdmin, depending on what database you are using.

database

Now, we can create the corresponding model file for this table.

1
php artisan make:model Home

By default, this generated model expects a table called homes, which doesn’t make sense here. So we need to tell Laravel that we are using the home table. And we want to be able to mass assign columns such as name, description and logo, so we need to create a $fillable property.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Home extends Model
{
    use HasFactory;

    protected $table = 'home';
    protected $fillable = ['name', 'description', 'logo'];

}

Views and templates

For now, we need only three pages to make this sample homepage work. We need a create page to create the required data, a success page that tells you the required data has been successfully created, and finally, a home page that displays the data. And don’t forget the layout.

layout.blade.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />


    @yield('meta')
    
    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

</head>

<body>
    <!-- Responsive navbar-->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="#">{{ $name }}</a>
            ...
        </div>
    </nav>
    <!-- Page content-->
    <div class="container">
        @yield('content')
    </div>
    <!-- Bootstrap core JS-->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous">
    </script>
</body>

</html>

create.blade.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@extends('layout')

@section('meta')
    <meta name="description" content="create homepage" />
    <title>Create Homepage</title>
@endsection

@section('content')
    <br>
    <form action="/home" method="POST" enctype="multipart/form-data">
        {{ csrf_field() }}
        <div class="mb-3">
            <label for="inputName" class="form-label">Website Name</label>
            <input type="text" class="form-control" id="inputName" name="name">
        </div>
        <div class="mb-3">
            <label for="inputDescription" class="form-label">Website Description</label>
            <textarea type="text" class="form-control" id="inputDescription" name="description"></textarea>
        </div>
        <div class="mb-3">
            <label for="inputLogo" class="form-label">Choose a logo</label>
            <input type="file" class="form-control" id="inputLogo" name="logo">
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
@endsection

There are two things we need to notice when we use forms to transfer data in Laravel. The first one is csrf_field(). CSRF is a malicious attack targeting web applications, and this csrf_field() function provides protection against that type of attack. You can read more about CSRF (Cross-site request forgery) here.

The second thing we need to pay special attention to is the name attribute in every <input> or <textarea> element. When the form is submitted, the user input will be tied to a variable, whose name is specified by the name attribute. For instance, when name="description", the user input will be tied to the variable description, and we can access its value using $request->input('description'). We’ll see how this works later.

success.blade.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@extends('layout')

@section('meta')
    <meta name="description" content="{{ $description }}" />
    <title>{{ $name }}</title>
@endsection

@section('content')
    <div class="text-center mt-5">
        <h1>Success!</h1>
    </div>
@endsection

home.blade.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@extends('layout')

@section('meta')
    <meta name="description" content="{{ $description }}" />
    <title>{{ $name }}</title>
@endsection

@section('content')
    <div class="text-center mt-5">
        <img src="{{ $logo }}" width="200px">
        <h1>{{ $name }}</h1>
        <p>{{ $description }}</p>
    </div>
@endsection

Routes and controllers

Now we have the database and the views ready, we can start writing the logic for our homepage. Let’s create a HomeController.

1
php artisan make:controller HomeController --resource

This controller contains seven different methods and each of them has a different function. In this article, I’m going to walk you through index(), create() and store(). As for the rest, even though they have different purposes, they do share the same logic.

Next, we create routes for these methods.

1
Route::resource('home/', HomeController::class);

This line of code will create a route for each method in HomeController, we can list all of them like this:

1
php artisan route:list

laravel routes

Create and Store

Let’s start with create().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('create', [
            'name' => 'Create A Homepage'
        ]);
    }

This is an easy one, we hardcoded and passed one variable name to the view, because the navbar requires the variable, but it is not in the database yet. We can test it by visiting http://127.0.0.1:8000/home/create

homepage

Now we can fill in the data, and when we click the Submit button, the browser will go to /home with a POST method, which triggers the store() method.

store method

The store() method is a little bit tricky to deal with. First, there are two things we need to import, the Home model, since we need to access the database, and Storage, since we need to upload a file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

namespace App\Http\Controllers;

use App\Models\Home;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class HomeController extends Controller
{
    ...

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // Get the data from the request
        $name = $request->input('name');
        $description = $request->input('description');
    
        // Create a new Home and put the requested data to the corresponding column
        $home = new Home;
        $home->name = $name;
        $home->description = $description;
    
        // Dealing with image
        $logo_path = $request->file('logo')->store('logos', 'public');
        $home->logo = $logo_path;
    
        // Save the data
        $home->save();
    
        $home = Home::all()->first();
        
        return view('success', [
            'name' => $home->name,
            'description' => $home->description,
        ]);
    }
    
    ...

} 

The text-based data is relatively easy to deal with. We use the input() method to retrieve the data from the request. Remember that the argument for input() must match the name attribute. And then, we can create a new Home record, and put the data in the corresponding column.

When dealing with images, we first use file('logo') to get the image file, and store('logos', 'public') sends the image to the logos folder in the public disk. In Laravel, there are a few disks defined, and the configuration file is located at config/filesystems.php. Each disk points to a different location in the Laravel file system.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 /*
    |--------------------------------------------------------------------------
    | Filesystem Disks
    |--------------------------------------------------------------------------
    |
    | Here you may configure as many filesystem "disks" as you wish, and you
    | may even configure multiple disks of the same driver. Defaults have
    | been setup for each driver as an example of the required options.
    |
    | Supported Drivers: "local", "ftp", "sftp", "s3"
    |
    */

    'disks' => [
    
        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
        ],
    
        'public' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',
        ],
    
        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),
            'endpoint' => env('AWS_ENDPOINT'),
            'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
        ],
    
    ],

In our case, we are using the public disk and it points to the /my-app/storage/app/public directory. And as we specified in the store() method, Laravel will create a logos folder and put our uploaded image inside.

upload image

Notice that Laravel automatically changes the name of the image file to avoid any conflicts. And finally, store() will return a path, which points to the location of this image file. This is what is stored in the database.

image path

Index

Now that we have the required data in the database, we can try to display that data. Since we only have one record in the database, using the index() method makes more sense than show().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $home = Home::all()->first();

        return view('home', [
            'name' => $home->name,
            'description' => $home->description,
            'logo' => Storage::url($home->logo),
        ]);
    }

Storage::url($home->logo) will generate the URL that points to the image file. This only works if you’ve created the symbolic link using the following command.

1
php artisan storage:link

This creates a link from public/storage to storage/app/public. The URL points to the public folder, and it can only access this folder through this symbolic link.


comments powered by Disqus