Paperback Practical Laravel
Paperback Practical Laravel
Paperback Practical Laravel
Practical Books
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or
transmitted in any form or by any means, without the author’s prior written permission, except in
the case of brief quotations embedded in critical articles or reviews.
Every effort has been made to prepare this book to ensure the accuracy of the information
presented. However, the information contained in this book is sold without warranty, either express
or implied. Neither the author or its distributors, will be held liable for any damages caused or
alleged to have been caused directly or indirectly by this book.
Contributors
About the author
Daniel Correa has been a researcher and a software developer for several years. Daniel has a Ph.D.
in Computer Science; currently, he is a professor at Universidad EAFIT in Colombia. He is interested
in software architectures, frameworks (such as Laravel, Django, Nest, Express, Vue, React, Angular,
and many more), web development, and clean code.
Daniel is very active on Twitter. He shares tips about software development and reviews software
engineering books. Contact Daniel on Twitter at @danielgarax.
3
Practical Laravel
Table of Contents
Preface ................................................................................................................................................ 6
Chapter 01 – Introduction ................................................................................................................... 8
Chapter 02 – Online Store Running Example .................................................................................... 11
Chapter 03 – Introduction to Laravel and Installation ...................................................................... 13
Chapter 04 – Introduction to MVC applications ............................................................................... 18
Chapter 05 – Layout View ................................................................................................................. 20
Chapter 06 – Index and About Pages ................................................................................................ 27
Chapter 07 – Refactoring Index and About Pages ............................................................................ 32
Chapter 08 – Use of a Coding Standard ............................................................................................ 38
Chapter 09 – List Products with Dummy Data .................................................................................. 41
Chapter 10 – Configuration of MySQL Database .............................................................................. 47
Chapter 11 – Product Migration ....................................................................................................... 50
Chapter 12 – Product Model ............................................................................................................. 56
Chapter 13 – List Products with Database Data ................................................................................ 58
Chapter 14 – Refactoring List Products ............................................................................................. 61
Chapter 15 – Admin Panel................................................................................................................. 70
Chapter 16 – List Products in Admin Panel ....................................................................................... 76
Chapter 17 – Create Products ........................................................................................................... 80
Chapter 18 – Create Products with Images....................................................................................... 89
Chapter 19 – Edit and Delete Products ............................................................................................. 94
Chapter 20 – Refactoring Validations ............................................................................................. 104
Chapter 21 – Login System .............................................................................................................. 107
Chapter 22 – Refactoring User ........................................................................................................ 113
Chapter 23 – AdminAuthMiddleware ............................................................................................. 121
Chapter 24 – Introduction to Web Session ..................................................................................... 124
Chapter 25 – Shopping Cart ............................................................................................................ 127
Chapter 26 – Orders and Items ....................................................................................................... 135
Chapter 27 – Product Purchase....................................................................................................... 149
Chapter 28 – Orders Page ............................................................................................................... 156
Chapter 29 – Deploying to the Cloud – Clever-Cloud – MySQL Database ...................................... 165
Chapter 30 – Deploying to the Cloud – Heroku – Laravel Application............................................ 171
4
Practical Laravel
5
Practical Laravel
Preface
Laravel is a PHP web application framework with expressive and elegant syntax. We will use Laravel
to develop an Online Store application which uses several Laravel features. The Online Store
application will be the means to understand straightforward and complex Laravel concepts and how
Laravel features can be used to implement real-world applications.
The main difference between this book and other similar books is that this book is not just about
Laravel. Instead, this book is about a “clean” design and implementation of web applications using
Laravel. By ‘clean’, we refer to an understandable, maintainable, usable, and well-divided
application.
The authors have developed several applications over many frameworks, including Laravel, Django,
Express, Flask, Nest, Spring, Vue, Angular, and React. Moreover, we are going to use that knowledge
to create a clean design and clean code strategies that can be applied not just to Laravel, but to the
design and implementation of most web applications using frameworks such as Django, Nest, Flask,
Express, and more.
This book is written with brief explanations direct to the point. It includes tips, short discussions,
and useful phrases found in other books that we have read to provide you with a practical approach
that will make improve your coding skills.
This is a short book divided into 31 chapters, with six pages on average per chapter. It was designed
not to overwhelm you. With this division, you will feel like you are making fast progress. We won’t
cover all Laravel features, but some of the most important to develop MVC web applications.
We hope you enjoy this journey as we did when we wrote this book.
This book is for web developers or programmers who want to learn Laravel and improve their code
skills. No previous knowledge of Laravel is required. However, basic programming knowledge is
required. This book is also suitable for experienced Laravel developers. They can revise previous
concepts and learn new clean code strategies.
You can download the example code files from the GitHub repository
https://github.com/PracticalBooks/Practical-Laravel. In it, you will find the code of each chapter.
You can replicate this book’s code or download the code directly from GitHub. If there is an update
to the code, it will be updated on the existing GitHub repository.
If you have questions about any aspect of this book or want to discuss something, we recommend
you use the discussion zone of the GitHub repository (see Fig. P-1). In that way, you can learn from
6
Practical Laravel
other questions, and we can learn from you. Besides, others in the community can answer your
questions.
Additionally, you can email your questions to practicalbooksco@gmail.com. Please mention the
book title in the subject of your message.
We also provide a PDF file with colored images of the figures/diagrams used in this book. You can
download it here: https://github.com/PracticalBooks/Practical-
Laravel/blob/main/BookImages/Book%20images.pdf.
If you want to receive book updates, please email us at practicalbooksco@gmail.com. We will also
subscribe you to our mailing list.
7
Practical Laravel
Chapter 01 – Introduction
We will begin our journey to understand and apply many Laravel concepts and features to develop
MVC web applications.
The book is divided into the following chapters. We will highlight the Laravel concepts we will learn
and the features and tools we will use across the chapters.
• Chapter 01 – Introduction.
• Chapter 02 – Online Store running example.
• Chapter 03 – Introduction to Laravel and Installation: Laravel, XAMPP, Artisan, and
Composer.
• Chapter 04 – Introduction to MVC applications.
• Chapter 05 – Layout View: Blade, Bootstrap, and views.
• Chapter 06 – Index and About Pages: views, routes, and controllers.
• Chapter 07 – Refactoring Index and About Pages.
• Chapter 08 – Use of a Coding Standard: PHP_CodeSniffer.
• Chapter 09 – List Products with Dummy Data
• Chapter 10 – Configuration of MySQL Database: MySQL and phpMyAdmin.
• Chapter 11 – Product Migration: migrations.
• Chapter 12 – Product Model: models and Eloquent.
• Chapter 13 – List Products with Database Data.
• Chapter 14 – Refactoring List Products.
• Chapter 15 – Admin Panel.
• Chapter 16 – List Products in Admin Panel.
• Chapter 17 – Create Products: forms and validations.
• Chapter 18 – Create Products with Images: storage.
• Chapter 19 – Edit and Delete Products.
• Chapter 20 – Refactoring Validations.
• Chapter 21 – Login System: laravel/ui.
• Chapter 22 – Refactoring User: Tinker.
• Chapter 23 – AdminAuthMiddleware: Middleware and authentication.
• Chapter 24 – Introduction to Web Session: Laravel sessions.
• Chapter 25 – Shopping Cart.
• Chapter 26 – Orders and Items: Eloquent relationships.
• Chapter 27 – Product Purchase.
• Chapter 28 – Orders Page: eager loading and debugbar.
• Chapter 29 – Deploying to the Cloud – Clever-Cloud – MySQL Database: Clever-Cloud.
• Chapter 30 – Deploying to the Cloud – Heroku – Laravel Application: Heroku.
• Chapter 31 – Continue your Laravel Journey.
In this book, we will develop an Online Store. This Online Store will serve us to understand some of
the more important Laravel concepts. Figures 1-1, 1-2, 1-3, and 1-4 show the kind of application we
will develop.
8
Practical Laravel
9
Practical Laravel
10
Practical Laravel
Online Store is a web application where users place orders to buy products.
The Online Store will be implemented with Laravel (PHP), MySQL database, Bootstrap (a CSS
framework), and Blade (a Laravel templating system). We will learn about these elements in the
upcoming chapters.
Below is a class diagram illustrating the application scope and design (see Fig. 2-1). We have a User
class with its data (id, name, email, password, etc.) which can place Orders. Each Order is composed
of one or more Items that are related to a single Product. Each Product will have its corresponding
data (id, name, description, image, etc.).
11
Practical Laravel
This book is not about class diagrams, so we won’t explain other details in the class diagram. You
will see a relationship between the code and this diagram as you advance through the book. This
diagram serves as a blueprint for the construction of our application.
TIP: Designing a class diagram before starting to code helps us understand the
application’s scope and identify important data. It also helps us know how the
application elements are related. You can share a diagram like this with your team
or colleagues, obtain quick feedback, and make adjustments as needed. Since it is a
diagram, changes can be made quickly. Else, when the project has been coded, the
replacement cost will be higher to move data from one class to another. Let’s check
this phrase from (2015 – Newman, S. - Building microservices) book. “I tend to do
much of my thinking in the place where the cost of change and the cost of mistakes
is as low as it can be: the whiteboard.”
Now that we considered the kind of application we want to build, let’s next understand what Laravel
is and how to install it.
12
Practical Laravel
Laravel is a free and open-source PHP framework that provides a set of tools and resources to build
modern PHP web applications (https://laravel.com/). Laravel provides an elegant syntax, built-in
features, and various compatible packages and extensions.
Laravel aims to make the development process a pleasing one for the developer without sacrificing
application functionality. “Happy developers make the best code” (written in the Laravel
documentation).
When writing this book, the latest version is Laravel 9, which we will use to build our Online Store
application.
Note: a new Laravel version might be available at the time you are reading this book. We
recommend you continue using Laravel 9 for this project. Once you complete this book, you can
upgrade to the latest Laravel version. In this way, most of the code will remain reusable. Some
others might require minor adjustments.
There are several ways to install Laravel. You can check some of them here:
https://laravel.com/docs/9.x#your-first-laravel-project. We will use a local installation for this book,
which requires installing XAMPP and Composer.
XAMPP
XAMPP is the most popular PHP development environment. XAMPP is a free, easy to install Apache
distribution containing MySQL, PHP, and Perl. If you don’t have XAMPP installed, go to
https://www.apachefriends.org/download.html. Download and install it. Be careful, you will need
to download and install a XAMPP version which supports PHP 8 because Laravel 9 requires PHP 8.
Once XAMPP is installed, go to the Terminal, and execute the following command. Note: if PHP is
not recognized as an internal or external command, it means you need to add the XAMPP PHP
installation folder to your PATH environment variable. A simple search in Google will help you to
solve this.
Execute in Terminal
php --version
If the installation was successful, you would see a result as presented in Fig 3-1. Please check you
have PHP 8.* installed.
13
Practical Laravel
Composer
Composer is a tool for dependency management in PHP. It allows you to declare the libraries your
project depends on, and it will manage (install/update) them for you. If you don’t have Composer
installed, go to https://getcomposer.org/download/. Download and install it.
Once Composer is installed, go to the Terminal, and execute the following command.
Execute in Terminal
composer --version
If the installation was successful, you would see a result as presented in Fig 3-2.
Open your Terminal, and in a location of your choice (you can use the xampp/htdocs/ location if you
want), execute the following:
Execute in Terminal
composer create-project laravel/laravel onlineStore "9.*" --prefer-dist
The previous command creates a new Laravel 9.* project inside the onlineStore folder. Next, in your
Terminal, move to the onlineStore folder, and run the application with the following:
Execute in Terminal
cd onlineStore
php artisan serve
The php artisan serve command starts Laravel's server (see Fig. 3-3). Artisan is the command line
interface included with Laravel. Artisan exists at the root of your application as the artisan script and
provides helpful commands to assist you while you build your application. More information about
Laravel Artisan can be found here: https://laravel.com/docs/9.x/artisan.
14
Practical Laravel
If the installation and setup were successful, you could open the Laravel development server link in
your browser (http://127.0.0.1:8000/). You should see your Laravel 9 application as shown in Fig. 3-
4.
Note: you can stop the server with Ctrl + C (on Windows) or Cmd + C (on Mac).
Fig. 3-5 shows the Laravel project structure. We won’t explain all the folders and files since we want
to start developing our web applications quickly. We will explain some of the more important ones.
The others will be covered in upcoming chapters.
15
Practical Laravel
16
Practical Laravel
17
Practical Laravel
As you can see, structuring your code is not an easy task. That is the reason why developers and
computer scientists have developed what are called software architectural patterns. Software
architectural patterns are structural layouts used to solve commonly faced software design
problems. With these patterns, startups and novice developers don’t have to “reinvent the wheel”
each time they start a new project. There are many architectural patterns, such as model-view-
controller, layers, service-oriented, and micro-services. Each one has its advantages and
disadvantages. Many are widely adopted. Still, one of the most used is the model-view-controller
pattern.
Laravel provides support for the MVC pattern thanks to the integration of the Blade templating
engine. Other similar frameworks provide support to this popular pattern too. We will see this
pattern in action (with actual code) later.
The MVC pattern provides some advantages: better code separation, multiple team members can
work and collaborate simultaneously, finding an error is easier, and maintainability is improved. Fig.
4-1 shows the Online Store software architecture we will implement in this book. It can be a little
overwhelming now, but you will understand the elements of this architecture when you finish this
book. We will review the architecture in the final chapters.
18
Practical Laravel
We highlight the Model, View, and Controller layers in grey. We have four models (entities)
corresponding to the classes defined in our class diagram (in Fig. 2-1). As mentioned, there are
different approaches to implement web applications with Laravel. There are even different versions
of MVC used in a Laravel application. In the following chapters, we will see the advantages of
adopting the MVC architecture defined in Fig. 4-1.
19
Practical Laravel
Blade is a powerful templating engine that is included with Laravel. Blade template files use the
.blade.php file extension and are typically stored in the resources/views directory. In your blade files,
you will have a mix of HTML code with Blade directives and Blade elements. Blade directives are
convenient shortcuts for common PHP control structures, such as conditional statements and loops.
For example, the following code shows an excerpt of a simple view in Laravel using plain PHP.
Analyze Code
<?php if($records > 0) { ?>
I have records!
<?php } else { ?>
I don't have any records!
<?php } ?>
The same view, but with Blade directives, is presented next. It looks cleaner.
Analyze Code
@if (count($records) > 0)
I have records!
@else
I don't have any records!
@endif
Quick discussion: You don’t need a templating engine in PHP projects. You can mix
app logic code (PHP) with app view code (HTML) in any PHP file. However, this multi-
language combination is not supported for other languages such as Java or Python.
So, why do PHP frameworks use templating engines? The main reason is to avoid
using PHP syntax or PHP tags inside your view files. Instead, you should use the
templating engine directives or helpers. What is the advantage? The template
engines limit the number of available functionalities implemented in views (to
provide a proper code separation). For example, with PHP and HTML you can create
a database connection or even a PHP class inside a view file (which is crazy because
views should not be responsible for creating database connections or classes). So,
template engines ensure that you won’t make crazy things in your views. Our
recommendation is: if you don’t find a directive or helper for a functionality you
need to implement in a view file, it is because that functionality should not be
implemented in the view (maybe it should be implemented in a controller or in
another file).
20
Practical Laravel
TIP: Do not use plain PHP code in your views. Blade allows it, but please do not do
it. Blade contains a @php directive that will enable you to inject plain PHP code.
However, only use it as your last resort. We have developed complex Laravel web
applications without the use of that directive.
Introducing Bootstrap
Bootstrap is the most popular CSS framework for developing responsive and mobile-first websites
(see Fig. 5-1). For Laravel projects, a developer can design the user interface from scratch if he wants
to. But because this book is not about user interfaces, we will take advantage of CSS frameworks
(such as Bootstrap) and use a starter template to create something that looks professional. You can
find out more about Bootstrap at: https://getbootstrap.com/.
Most web applications maintain the same general layout across various pages (common header,
navigation bar, and footer). However, maintaining our application would be incredibly cumbersome
if we had to repeat the entire header, navbar and footer HTML in every view. Fortunately, we can
define this layout as a single Blade file and use it throughout our application.
Creating app.blade.php
To get started with Bootstrap and the Blade layout, we first create a folder called layouts under the
resources/views directory. We then use the Bootstrap starter template to create our layout (see Fig.
5-2). The Bootstrap starter template can be found here:
https://getbootstrap.com/docs/5.1/getting-started/introduction/.
21
Practical Laravel
In resources/views/layouts, create a new file app.blade.php and fill it with the following code.
22
Practical Laravel
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous">
</script>
</body>
</html>
The above code is based on the Bootstrap starter template code and the Bootstrap Navbar
(https://getbootstrap.com/docs/5.1/components/navbar/). We modified the base code, including
links in the header (Home and About). The starter template includes a Bootstrap CSS file
(bootstrap.min.css), and a Bootstrap JS file (bootstrap.bundle.min.js). We included three @yield
Blade directives.
@yield is used as a marker. We will inject code in those markers from child Blade views (using the
@section directive). @yield uses two parameters, the first is the marker identifier. The second is a
default value that will be injected if a child view does not inject code for that marker.
Modifying welcome.blade.php
Delete all the existing code in resources/views/welcome.blade.php and fill it with the following code.
The welcome view extends the layouts.app view. This view injects the 'Home Page - Online Store'
message in the @yield('title') of the layouts.app and injects an HTML div with a welcome message
inside the @yield('content') of the layouts.app.
23
Practical Laravel
Execute in Terminal
php artisan serve
Open the browser to http://127.0.0.1:8000/, and you will see the application with the new layout
(see Fig. 5-3). If you reduce the browser window width, you will see a responsive navbar (thanks to
the bootstrap starter template, navbar, and the inclusion of the bootstrap files, see Fig. 5-4).
Figure 5-4. Application home page with reduced browser window width.
Let’s make our app interface more professional. We will include a custom CSS file and a footer in
our layout.
Create a folder css under the public/ directory. Then, in public/css, create a new file app.css and fill
it with the following.
.copyright {
background-color: #1a252f;
24
Practical Laravel
.bg-primary {
background-color: #1abc9c !important;
}
nav{
font-weight: 700;
}
.img-card{
height: 18vw;
object-fit: cover;
}
We have some custom CSS styles in the previous file. We override some Bootstrap elements with
our values and colors.
Modifying app.blade.php
25
Practical Laravel
Daniel Correa
</a> - <b>Paola Vallejo</b>
</small>
</div>
</div>
<!-- footer -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous">
</script>
</body>
</html>
Laravel includes a variety of global helper PHP functions. For example, the asset function generates
a URL for an asset using the current scheme of the request (HTTP or HTTPS). Since our css/app.css
file is inside the public folder, it will be automatically deployed over our server link (i.e.,
http://127.0.0.1:8000/css/app.css). We used curly braces {{ }} to invoke the asset function. Curly
braces are used in Blade files to display data passed to the view or invoke Laravel helpers. In the
end, we created a footer section with the book’s author names and links to their Twitter accounts.
Execute in Terminal
php artisan serve
You will see our home page, with the refined layout (see Fig. 5-5). It looks more professional (at least
for us). This design was inspired by a free Bootstrap template called Freelancer. You can find more
information about this template here: https://startbootstrap.com/theme/freelancer.
We will refine the welcome page and create an about page in the next chapter.
26
Practical Laravel
The home page currently just displays a welcome message. Let’s create a new index view. In
resources/views/, create a subfolder home. In resources/views/home, create a new file
index.blade.php and fill it with the following code.
The first @section injects the content of the viewData["title"] variable. That variable will be defined
in the web route file (presented later in this chapter) and passed to this view. Then, we define some
divisions to display some images. We need to download these images and store them in our public
folder. First, in the public folder, create a subfolder img. Then, download the following three images
from this link https://github.com/PracticalBooks/Practical-
Laravel/tree/main/Chapter06/onlineStore/public/img and store them inside the public/img folder
(see Fig. 6-1).
27
Practical Laravel
About view
Let’s create the About view. In resources/views/home, create a new file about.blade.php and fill it
with the following code.
We have a simple view that displays a description of the application and some information about
the author. We will pass four variables from a controller to this view later. Remember that Blade
allows curly braces to display data passed to the view.
Laravel Routing is a mechanism used to route all your application requests to specific methods or
functions which will deal with those specific requests. Laravel routes accept a URI (Uniform Resource
Identifier) along with a closure. Closures are PHP’s version of anonymous functions. A closure is a
28
Practical Laravel
function you can pass around as an object, assign to a variable, or pass as a parameter to other
functions and methods.
Laravel routes are defined in your route files (located in the routes directory).
• The routes/web.php file defines routes for your web interface. These are the routes that we
will use in this book.
• The routes/api.php file defines routes for your API (if you have one). These are routes used in
service-oriented architectures or REST APIs (which is out of the scope of this book).
Let’s check a couple of routes to understand how they work. In routes/web.php, make the following
changes in bold.
Defining all your request handling logic inside in your route files’ closures does not seem smart. You
will end with hundreds or thousands of code lines inside the route files (which affects the project
maintainability). A good strategy is to organize this behavior using “controller” classes. Controllers
can group related request handling logic into a single class. For example, a UserController class might
handle all incoming requests related to users, including showing, creating, updating, and deleting
users.
In app/Http/Controllers, create a new file HomeController.php and fill it with the following code.
29
Practical Laravel
namespace App\Http\Controllers;
use Illuminate\Http\Request;
}
}
We have the HomeController which extends the Laravel Controller class. We have the index method
that won’t be discussed in this chapter. Just ignore it for now, we will use it to exemplify an
important scenario in the next chapter. Then, we have the about method connected to the
(“/about”) route in the routes/web.php file. This method defines a set of variables and passes them
to the home.about view.
Recap, when a user goes to the (“/”) route, the home.index view will be displayed (delivered in the
routes/web.php file). Likewise, when a user goes to the (“/about”) route, the home.about view will
be displayed (delivered in the HomeController about method).
30
Practical Laravel
Go to the (“/”) route, and you will see the new home page (see Fig. 6-2). Then, go to the (“/about”)
route, and you will see the about page (see Fig. 6-3).
We created a couple of Laravel views, a Laravel Controller, and modified the web routes. However,
we have not linked the navbar menu to the new routes. We have also purposely introduced some
serious mistakes when defining the previous elements. Why? It is because we want to illustrate
the concept of clean code. In the next chapter, we will refactor these elements and apply some
strategies about clean code in routes, controllers, and views.
31
Practical Laravel
Refactoring routes
Previous routes
The previous chapter showed two routes defined with different approaches. Let’s check the first
one.
Analyze Code
Route::get('/', function () {
$viewData = [];
$viewData["title"] = "Home Page - Online Store";
return view('home.index')->with("viewData", $viewData);
});
It is one of the worst approaches you can apply (no matter the framework you are using). If you put
the application logic in the route file, you will end with hundreds or thousands of code lines in those
files as your project evolves, and maintainability will be the hell. So, we don’t recommend using this
strategy even for small projects. Our recommended strategy will be presented next.
Analyze Code
Route::get('/about', 'App\Http\Controllers\HomeController@about')->name("home.about");
It looks better. Our route is now connected to a controller method. We also put a name on that
route. We recommend a combination of the controller’s name plus the controller’s method name
to define the route name.
New routes
Let’s refactor our routes. In routes/web.php, remove the previous (“/”) route, and replace it for the
following route in bold.
32
Practical Laravel
$viewData = [];
$viewData["title"] = "Home Page - Online Store";
return view('home.index')->with("viewData", $viewData);
});
Route::get('/', 'App\Http\Controllers\HomeController@index')->name("home.index");
Route::get('/about', 'App\Http\Controllers\HomeController@about')->name("home.about");
Quick discussion: Did you check the routes we defined in the previous chapter? Now
think that there is a new member in your project. And the new member must
include a new route in the routes/web.php file. Which approach will the member
use? The first one? The second one? That is a big problem. So, a good strategy is to
use the same approach for all routes as we did in the above code. Now new
members will replicate this approach. You can also complement this idea with the
next tip.
Refactoring controllers
Previous controller
The previous chapter showed a controller with two methods that used different approaches. Let’s
check the about method.
Analyze Code
public function about()
{
$data1 = "About us - Online Store";
$data2 = "About us";
$description = "This is an about page ...";
$author = "Developed by: Your Name";
return view('home.about')->with("title", $data1)
->with("subtitle", $data2)
->with("description", $description)
33
Practical Laravel
->with("author", $author);
Quick discussion: Let’s see the importance of variable naming with two quotes from
the (2019 - Thomas, D., & Hunt, A. - The Pragmatic Programmer: your journey to
mastery) book. “The beginning of wisdom is the ability to call things by their right
names. - Confucius.” - “Why is naming important? Because good names make code
easier to read, and you have to read it to change it.”
Now, let’s analyze the index method (which was ignored in the previous chapter).
Analyze Code
public function index()
{
$viewData = [];
$viewData["title"] = "Home Page - Online Store";
return view('home.index')->with("viewData", $viewData);
}
In this case, we only define one variable called viewData which contains all the data sent to the view.
This variable is an associative array. With this approach, it does not matter if we pass one data to
the view or dozens. In both cases, we just pass the associative array.
TIP: In your architectural rules document, you can include a new rule saying that
controllers should only pass an associative array called viewData to the views.
Laravel provides many ways to pass that data to the view, it does not matter which
one you choose. However, it is essential to mention the selected approach in the
rules document to be used consistently by all team members through the
application.
New controller
34
Practical Laravel
}
}
Now that we use the single variable strategy, both methods are consistent. We suggest storing the
views following the next approach. First, we create a subfolder with the controller’s name inside the
resources/views folder. In this case, the subfolder is called home. And if a controller method displays
data, we create a file with the controller’s method name inside the view controller subfolder. So,
we have the about.blade.php file stored inside the resources/views/home subfolder. It makes easier
to find specific views for specific controller methods.
Refactoring views
The previous chapter showed two views that display data a little differently. The home/index view
won’t be changed since it displays the data using the viewData strategy. However, we will need to
modify the home/about view to match the single variable strategy previously defined.
35
Practical Laravel
As you can see, we now access the data through the single viewData associative array. We will use
this strategy across the entire application, making our views more consistent.
Quick discussion: The previous data can also be placed directly over the previous
views. We mean, you don’t need to define those texts as variables in the controller
and send them to the view. Instead, you can place the text directly in the views as
we did in our welcome.blade.php view. We did it that way to illustrate and explain
some Laravel elements. There is a better option that is out of this book’s scope
(which is what we prefer). That option is called Localization. In Localization, you
move away those texts from controllers and views and place them in the
resources/lang folder. Localization allows you even to retrieve strings in various
languages. It is not difficult to implement. You can use this link to get more info
about it https://laravel.com/docs/9.x/localization#introduction, search in Google,
or let us know if you need a good example (use the discussion zone of the book
repository).
Now that we have the proper routes, controller, and views, let’s include the links in the header. In
resources/views/layouts/app.blade.php, make the following changes in bold.
36
Practical Laravel
We use the route helper method in the previous layout, which generates a URL for a given named
route. We used the names of the routes defined for the (“/”) route (home.index) and the (“/about”)
route (home.about).
Execute in Terminal
php artisan serve
Now, you can navigate between the Home page and the About page by using the links in the
navigation bar (see Fig. 7-1).
Note: you can find the application code at the GitHub repository in
https://github.com/PracticalBooks/Practical-Laravel.
37
Practical Laravel
One of the most used PHP coding standards is PSR-2. It is a standard established by PHP-FIG (PHP
Framework Interop Group, more information about PSR-2 can be found at https://www.php-
fig.org/psr/psr-2/). Some tools help programmers to automatically check their code against these
coding standards. In this book, we will use PHP_CodeSniffer, which is presented next.
Introducing PHP_CodeSniffer
PHP_CodeSniffer is a set of two PHP scripts. The phpcs script tokenizes PHP, JavaScript, and CSS files
to detect violations based on a defined coding standard. The phpcbf script automatically corrects
coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your
code remains clean and consistent. More information about PHP_CodeSniffer can be found here:
https://github.com/squizlabs/PHP_CodeSniffer.
Execute in Terminal
composer require --dev squizlabs/php_codesniffer
The previous command installs the PHP_CodeSniffer library in our composer.json file, which
downloads and extracts the library files in the vendor folder.
We will apply a quick configuration of PHP_CodeSniffer. In the project root directory, create a new
file phpcs.xml and fill it with the following code.
38
Practical Laravel
<exclude-pattern>*/migrations/*</exclude-pattern>
</ruleset>
This file establishes that PHP_CodeSniffer will use PSR-2 as the coding style standard. It also defines
that when we run PHP_CodeSniffer, it will find errors in the app/*, config/*, database/*, and
routes/* folders. We exclude locations that include a migrations folder.
Running PHP_CodeSniffer
Execute in Terminal
./vendor/bin/phpcs
The previous command executes PHP_CodeSniffer based on the custom configuration defined in
the phpcs.xml file (see Fig. 8-1). Our case showed 17 errors, all with an “X” mark, which means that
PHP_CodeSniffer can handle them and fix them (if you want). Some of those errors are related to
spaces, indentation, and rules defined in the PSR2 coding style guide. For example, there was an
annoying blank line at the end of the about method of the HomeController (before the curly brace
closing). So, PHP_CodeSniffer marked it as an error.
To automatically fix it, we need to execute the PHP_CodeSniffer phpcbf file with the following
command (see Fig. 8-2).
Execute in Terminal
./vendor/bin/phpcbf
After that, it shows the errors fixed. This strategy is better than a manual fix. For the rest of the
book, we will use PHP_CodeSniffer to improve the code quality.
39
Practical Laravel
Final remarks
• There are other tools that you can use to extend the PHP_CodeSniffer capabilities. For
example, Larastan. Larastan is a static analysis tool for Laravel Projects
(https://github.com/nunomaduro/larastan), this tool even discovers bugs in your code
without running it.
• PHP_CodeSniffer does not work well with blade files. A formatting Visual Studio Code plugin
called Format HTML in PHP can help you to format your Blade views.
• PHP_CodeSniffer does not automatically fix all errors. For example, there is a standard of using
camelCase coding style to name methods. If you change the HomeController about method to
About, PHP_CodeSniffer will mark that error, but it won’t fix it automatically. You must fix this
error manually.
TIP: There is a good story about “The Broken Window Theory” described in the (2019
- Thomas, D., & Hunt, A. - The Pragmatic Programmer: your journey to mastery)
book. You can search it in Google. From that story, we want to highlight the next tip.
Don’t leave “broken windows” (e.g., bad designs, wrong decisions, or poor code)
unrepaired. Fix each one as soon as it is discovered. The previous three chapters
showed many broken windows, which fortunately were fixed.
TIP: Always use a coding standard tool, formatter, static code analysis tool, or even
a combination of them in your projects. It will save you much time and improve the
code quality. In addition, you will find linters available for most programming
languages. Besides, include a rule in your architectural rules document mentioning
that all code changes should be previously verified by using these tools. You can
even automate this process (with a pipeline or CI/CD strategy). However, it is out of
the scope of this book. You can check an article that Daniel wrote for a Laravel 8
project https://faun.pub/configure-laravel-8-for-ci-cd-with-jenkins-and-github-
part-1-58b9be304292.
40
Practical Laravel
Modifying routes
Let’s start by including a couple of routes to list products and show the data of a single product. In
routes/web.php, make the following changes in bold.
We will list all the application products in the first route (“/products”). The second route will be used
to show the data of a single product. “/products/{id}” takes a parameter called id. This parameter is
the product id to identify which product data to show. For example, if we access “/products/1”, the
application will display the data of the product with id=1.
If you noticed, both routes are connected to the ProductController. So, let’s implement it.
ProductController
In app/Http/Controllers, create a new file ProductController.php and fill it with the following code.
namespace App\Http\Controllers;
use Illuminate\Http\Request;
41
Practical Laravel
Analyze Code
public static $products = [
["id"=>"1", "name"=>"TV", "description"=>"Best TV", "image" => "game.png", "price"=>"1000"],
…
]
$products is an array that contains a set of products with its data. In the array index 0, we have the
product with id=1 (the “TV”). We have four dummy products. Currently, they are stored as a static
attribute of the ProductController class. We will later retrieve product data from a MySQL database
in upcoming chapters.
42
Practical Laravel
Analyze Code
public function index()
{
$viewData = [];
$viewData["title"] = "Products - Online Store";
$viewData["subtitle"] = "List of products";
$viewData["products"] = ProductController::$products;
return view('product.index')->with("viewData", $viewData);
}
The index method gets the array of products and sends them to the product.index view to be
displayed.
Analyze Code
public function show($id)
{
$viewData = [];
$product = ProductController::$products[$id-1];
$viewData["title"] = $product["name"]." - Online Store";
$viewData["subtitle"] = $product["name"]." - Product information";
$viewData["product"] = $product;
return view('product.show')->with("viewData", $viewData);
}
The show method gets an $id as a parameter (the id is collected from the URL). We extract the
product data with that id (check the bold line). We subtract one unit since we stored the product
with id=1 in the ProductController::$products array index 0, the product with id=2 in the
ProductController::$products array index 1, and so on. We then send the product data to the
product.show view.
Product views
Let’s first implement the product.index view, and then, the product.show view.
43
Practical Laravel
@section('subtitle', $viewData["subtitle"])
@section('content')
<div class="row">
@foreach ($viewData["products"] as $product)
<div class="col-md-4 col-lg-3 mb-2">
<div class="card">
<img src="{{ asset('/img/'.$product["image"]) }}" class="card-img-top img-card">
<div class="card-body text-center">
<a href="{{ route('product.show', ['id'=> $product["id"]]) }}"
class="btn bg-primary text-white">{{ $product["name"] }}</a>
</div>
</div>
</div>
@endforeach
</div>
@endsection
The important part of the previous code is the @foreach. @foreach is a Blade directive which allows
us to iterate over a list. We iterate through each product and display the product image and name.
More information about Blade directives can be found here:
https://laravel.com/docs/9.x/blade#blade-directives.
Analyze Code
<a href="{{ route('product.show', ['id'=> $product["id"]]) }}"
class="btn bg-primary text-white">{{ $product["name"] }}</a>
Finally, we put a link to the product name. The link will route to the product.show route (defined
previously in routes/web.php) and it requires a parameter to be sent. In this case, we send the
product id of the current iterated product.
In resources/views/product, create a new file show.blade.php and fill it with the following code.
44
Practical Laravel
We show the product image, name, and description in the above code. Remember, we are using
dummy products. This will change in upcoming chapters.
TIP: In the last examples, we have defined a structure to store our controllers,
controllers’ methods, routes’ names, and views. For example, the product.show
route is linked to the ProductController show method, which displays the
product/show view. Try to use this strategy across the entire project as it facilitates
finding the views of the corresponding controllers’ methods and vice versa.
Now, let’s include the products link in the header. In resources/views/layouts/app.blade.php, make
the following changes in bold.
45
Practical Laravel
Execute in Terminal
php artisan serve
Now, you can go to the Products page (see Fig. 9-1) and navigate to a specific product (see Fig. 9-2).
46
Practical Laravel
MySQL is the most popular Open-Source SQL database management system developed, distributed,
and supported by Oracle.
• MySQL is a database management system. A database is a structured collection of data. It
may be anything from a simple shopping list to a picture gallery or the vast amounts of
information in a corporate network. To add, access, and process data stored in a computer
database, you need a database management system such as MySQL.
• MySQL databases are relational. A relational database stores data in separate tables rather
than putting all the data in one big storeroom. The database structures are organized into
physical files optimized for speed. MySQL provides a logical model with objects such as
databases, tables, views, rows, and columns, which offers a flexible programming
environment.
MySQL tables
A table is used to organize data in rows and columns. It is used for both storing and displaying
records in a structured format. It is like worksheets in a spreadsheet application. The columns
specify the data type, whereas the rows contain the actual data. Below is how you could imagine a
MySQL table (see Fig. 10-1).
Almost every modern web application interacts with a database. Laravel makes interacting with
databases straightforward across a variety of supported databases. Laravel provides support for
four databases:
• MySQL 5.7+
• PostgreSQL 9.6+
• SQLite 3.8.8+
• SQL Server 2017+
In this project, we will work with MySQL. We will use the MySQL module that XAMPP (also WAMP)
provides.
47
Practical Laravel
Execute XAMPP, start the Apache Module, start the MySQL module, and click the MySQL Admin
button (of the MySQL module). It takes us to the phpMyAdmin application (see Fig. 10-2).
Note: If you are using WAMP or another similar application, the phpMyAdmin application can be
commonly accessed through the next route: http://localhost/phpmyadmin/.
In the phpMyAdmin application, enter your username and password. Default values are “root” (for
the username) and an empty password (see Fig. 10-3).
Once logged in to phpMyAdmin, click the Databases tab. Enter the database name “online_store”,
and click Create button (see Fig. 10-4).
48
Practical Laravel
We will use the user, password, and database name in the next chapter.
49
Practical Laravel
Now, we can just go to the phpMyAdmin application and create tables inside the online_store
database (such as the product table). It can be done through the fill of a form. It is the traditional
way. However, it has an issue, it does not allow us to have version control of our database tables
and queries. If you have ever had to tell a teammate to manually add a table column to their local
database schema after pulling in your changes from source control (such as GitHub), you have faced
this issue.
Laravel migrations are like version control for our database. They solve the previous issue allowing
us to define and share the application's database schema definition.
We won’t create database tables in the traditional way. Instead, we will do it through Laravel
migrations.
Product migration
Let’s create our product migration. In the Terminal, go to the project directory, and execute the
following:
Execute in Terminal
php artisan make:migration create_products_table
The previous command creates a products table migration file inside the database/migrations
folder. Each migration file name contains a timestamp that allows Laravel to determine the order of
the migrations. In our case, it created a file named
2022_02_11_153916_create_products_table.php.
If you look at the generated file, it contains a migration class. Migration classes contain two
methods: up and down. The up method is used to add new tables, columns, or indexes to your
database. In contrast, the down method should reverse the operations performed by the up
method.
Now, open the previously generated file. Delete all the existing code and fill it with the following
code.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
50
Practical Laravel
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('products');
}
};
Analyze Code
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
51
Practical Laravel
$table->string('name');
$table->text('description');
$table->string('image');
$table->integer('price');
$table->timestamps();
});
}
The up method will create a new database table called products. By default, Laravel suggests
creating the table names in plural. It is due to how the Laravel ORM “Eloquent” system works
(discussed later). The up method defines the creation of the products table with five columns (id,
name, description, image, and price). Therefore, a timestamps method will add two columns
(created_at and updated_at). We also used five column types (id, string, text, integer, and
timestamps). More information about available column types can be found here:
https://laravel.com/docs/9.x/migrations#available-column-types.
Analyze Code
public function down()
{
Schema::dropIfExists('products');
}
The down method contains the opposite of the up method. It drops the products table. We will see
how to execute the migration classes up and down methods later. But first, we need to update our
database credentials in our Laravel project.
TIP: Always define your database schema using migrations or similar approaches.
Remember, they work as version control of your databases. Most web application
frameworks provide these kinds of features. For example, Django provides Django
Migrations which work like the previous approach.
To execute the migrations, we need to modify the .env file (located at the project root folder). In the
.env file, make the following changes in bold. You need to set the DB_DATABASE, DB_USERNAME,
and DB_PASSWORD. If you have a different database name, username, or password, make the
corresponding changes.
52
Practical Laravel
DB_DATABASE=online_store
DB_USERNAME=root
DB_PASSWORD=
...
To run the migrations, in the Terminal, go to the project directory, and execute the following:
Execute in Terminal
php artisan migrate
The previous command executes the migrations defined in the database/migrations folder. Five
migrations are executed. The last one is our “create products table” migration. The other migrations
correspond to Laravel default migrations. We will take advantage of some of them in upcoming
chapters. If everything works as expected, you should see a result as presented in Fig. 11-1.
It is time to verify that the migrations were properly applied in our database. Go to the phpMyAdmin
application and click over the online_store database (see Fig. 11-2).
53
Practical Laravel
As you can see, our products table appears listed. Migrations worked!
Inserting products
Let’s insert four products into our database. For now, we will insert it manually (through SQL
queries). Later, we will insert products through a form in an upcoming chapter.
In phpMyAdmin, click the online_store database, click the SQL tab, paste the following SQL queries,
and click go (see Fig. 11-3).
Execute in Database
INSERT INTO products (id, name, description, image, price, created_at, updated_at) VALUES (NULL,
'TV', 'Best TV', 'game.png', '1000', '2021-10-01 00:00:00', '2021-10-01 00:00:00');
INSERT INTO products (id, name, description, image, price, created_at, updated_at) VALUES (NULL,
'iPhone', 'Best iPhone', 'safe.png', '999', '2021-10-01 00:00:00', '2021-10-01 00:00:00');
INSERT INTO products (id, name, description, image, price, created_at, updated_at) VALUES (NULL,
'Chromecast', 'Best Chromecast', 'submarine.png', '30', '2021-10-01 00:00:00', '2021-10-01 00:00:00');
INSERT INTO products (id, name, description, image, price, created_at, updated_at) VALUES (NULL,
'Glasses', 'Best Glasses', 'game.png', '100', '2021-10-01 00:00:00', '2021-10-01 00:00:00');
54
Practical Laravel
Let’s check that the products were successfully inserted. In phpMyAdmin, click the online_store
database, and click the products table. Hopefully, you will see the four products inserted (see Fig.
11-4).
55
Practical Laravel
Eloquent is a Laravel object-relational mapper (ORM) that makes it super easy to interact with our
database. When using Eloquent, each database table has a corresponding “Model” used to interact
with that table. Eloquent models allow you to insert, retrieve, update, and delete records from the
database tables. More information about Laravel Eloquent can be found here:
https://laravel.com/docs/9.x/eloquent.
Laravel models are located in the app/Http/Models folder. In that folder, you will find a User.php file
that contains a Laravel predefined User class model. We will focus on our Product model for now,
so let’s create it.
Execute in Terminal
php artisan make:model Product
You will see the Product.php file inside the app/Models folder. Let’s analyze the Product model code.
Analyze Code
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
It seems to be a little empty. We have a Product class that extends a Laravel Eloquent Model class.
The Laravel Eloquent Model class will provide our Product class with a set of useful methods and
attributes. Those methods will make it super easy to communicate with the database. Therefore, it
uses a HasFactory trait (but we will skip it).
56
Practical Laravel
Key methods
Let’s discuss some key methods that Eloquent provides to our models.
• Product::all(): retrieve all product records.
• Product::find(1): retrieve the product with id 1.
• Product::findOrFail(1): it is similar to the previous one, but it will throw an exception if no
result is found.
• Product::create(['name' => 'TV', ...]): create a new record in the database. You must pass an
associative array with the data to be assigned, id is not required since it is autogenerated.
• Product::destroy(1): remove the product with id 1.
We will use some previous methods in upcoming chapters. For now, let’s modify our application to
extract the product data from our MySQL database.
57
Practical Laravel
Modifying ProductController
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
58
Practical Laravel
{
$viewData = [];
$product = Product::findOrFail($id);
$viewData["title"] = $product["name"]." - Online Store";
$viewData["subtitle"] = $product["name"]." - Product information";
$viewData["product"] = $product;
return view('product.show')->with("viewData", $viewData);
}
}
Analyze Code
use App\Models\Product;
Analyze Code
public static $products = [
["id"=>"1", "name"=>"TV", "description"=>"Best TV", "image" => "game.png", "price"=>"1000"],
["id"=>"2", "name"=>"iPhone", "description"=>"Best iPhone", "image" => "safe.png",
"price"=>"999"],
["id"=>"3", "name"=>"Chromecast", "description"=>"Best Chromecast", "image" =>
"submarine.png", "price"=>"30"],
["id"=>"4", "name"=>"Glasses", "description"=>"Best Glasses", "image" => "game.png",
"price"=>"100"]
];
We remove the products dummy attribute since we don’t need it anymore. We will instead retrieve
the products data from the database.
Analyze Code
$viewData["products"] = Product::all();
We have the Product::all() in the index method, which retrieves the products from the database.
Analyze Code
$product = Product::findOrFail($id);
59
Practical Laravel
In the show method, we have the Product::findOrFail($id), which retrieves a specific product based
on its id. findOrFail could throw a ModelNotFoundException (i.e., when passing an invalid id). If the
ModelNotFoundException is not caught, a 404 HTTP response is automatically sent back to the client.
Execute in Terminal
php artisan serve
Go to the (“/products”) route, and you will see the products retrieved from our MySQL database.
Try to visit a specific product with an id that does not exist (i.e., “/products/21”). The application
will show a 404 error (see Fig. 13-1).
60
Practical Laravel
Model attributes
Open the Product model file and try to guess just by seeing that file which the Product model
attributes are. We have a problem here. We cannot deduce the Product model attributes. We must
look in our “create products table” migrations or open phpMyAdmin and go to the products table
just to find that answer. In our opinion, the way the Product model is designed affects the project
understandability. Many other MVC frameworks, such as Django (Python) or Spring (Java), explicitly
define their models’ product attributes, but that is not the Laravel case.
Let’s refactor our Product model. In app/Models/Product.php, make the following changes in bold.
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
61
Practical Laravel
We have declared the model’s attributes in the form of $this->attributes['id']. It is because Laravel
Eloquent stores the model’s attributes in a class array attribute called $attributes.
Check the following code to understand how the Laravel Eloquent model’s attributes work.
Analyze Code
$product = Product::findOrFail(1);
echo $product->name; # prints the product’s name
echo $product["name"]; # prints the product’s name
Laravel Eloquent provides two ways of accessing model attributes. The object attribute form
($product->name) and the associative array form ($product["name"]). Both options internally access
the Product model and look for the $this->attributes['name'] data. If that attribute does not exist, it
returns null. Otherwise, it returns the value stored in the class array attribute ($attributes). Spoiler,
we won’t use any of these forms of accessing the model’s attributes. We will use a better one, but
first, let’s see why we need a better way of accessing the model’s attributes.
Analyze Code
public function show($id)
{
$viewData = [];
$product = Product::findOrFail($id);
$viewData["title"] = $product["name"]." - Online Store";
$viewData["subtitle"] = $product["name"]." - Product information";
$viewData["product"] = $product;
return view('product.show')->with("viewData", $viewData);
}
62
Practical Laravel
We are accessing the product’s name through the associative array form ($product["name"]). Now,
let’s analyze the product/show.blade.php view.
Analyze Code
<div class="card-body">
<h5 class="card-title">
{{ $viewData["product"]["name"] }} (${{ $viewData["product"]["price"] }})
</h5>
<p class="card-text">{{ $viewData["product"]["description"] }}</p>
<p class="card-text"><small class="text-muted">Add to Cart</small></p>
</div>
Again, we are accessing the product’s name through the associative array form
($viewData["product"]["name"]).
What is the problem with accessing the entity data this way? Imagine that your boss tells you, “We
need to display all products’ names in uppercase throughout the entire application”. That is a big
issue as we extract products’ names over several different views and controllers. This simple
requirement will require us to modify several views and controllers. For now, let’s see what we must
do to achieve that requirement.
We should modify the ProductController.php controller this way (do not implement this change, it
is used just to exemplify this scenario):
Analyze Code
public function show($id)
{
$viewData = [];
$product = Product::findOrFail($id);
$viewData["title"] = strtoupper($product["name"])." - Online Store";
$viewData["subtitle"] = strtoupper($product["name"])." - Product information";
$viewData["product"] = $product;
return view('product.show')->with("viewData", $viewData);
}
And the product/show.blade.php view this way (do not implement this change, it is used just to
exemplify this scenario):
Analyze Code
<div class="card-body">
<h5 class="card-title">
{{ strtoupper($viewData["product"]["name"]) }} (${{ $viewData["product"]["price"] }})
63
Practical Laravel
</h5>
<p class="card-text">{{ $viewData["product"]["description"] }}</p>
<p class="card-text"><small class="text-muted">Add to Cart</small></p>
</div>
There are two significant issues with this strategy. (i) We have many duplicate codes throughout the
application (the strtoupper function is used over several places). And (ii) we must check all
controllers, all views, and maybe dozens or hundreds of files to check where we need to apply the
strtoupper function. It is not maintainable. So, let’s refactor our code to implement a better strategy.
First, let’s refactor our Product model. In app/Models/Product.php, make the following changes in
bold.
64
Practical Laravel
$this->attributes['name'] = $name;
}
65
Practical Laravel
For each Product attribute, we define its corresponding getter and setter. We will use getters and
setters to access and modify our model attributes. It has many advantages which we will talk about
later.
Now, we access the product attributes through the corresponding getters and setters.
66
Practical Laravel
Like the previous controller, we access the product attributes through the corresponding getters.
67
Practical Laravel
For now, the application looks the same. We only modified the way we access model attributes. So,
what is the advantage? Let’s revisit the boss requirement: “we need to display all products’ names
in uppercase over the entire application”.
In this case, we only need to modify the Product model file. Specifically, the getName method. Let’s
see the modification (you can apply the following change or leave it as it is).
Analyze Code
...
public function getName()
{
return strtoupper($this->attributes['name']);
}
...
If you run the application, you will see that all products’ names appear in uppercase. We only
required one single change in one specific location. That is the power of the use of getters or setters.
The definition and use of getters and setters guarantee a unique access point to the model
attributes. That is part of what some people call encapsulation, one of the three pillars of object-
oriented programming.
TIP: Always try to access your model attributes through getters and setters. It will
make it easier to add functionalities in the future. You can even include a new rule
saying that models’ attributes must be accessed through their corresponding getters
and setters (in your architectural rules document).
Quick discussion: Laravel provides another way to implement getters and setters.
Laravel calls them Accessors and Mutators (you can read more about it here:
https://laravel.com/docs/9.x/eloquent-mutators#accessors-and-mutators). We
don’t prefer this strategy for a single reason. When you use Accessors and Mutators
and access to the model attributes, you continue using the original form (i.e.,
$product->name) versus the classic getter form ($product->getName()). We prefer
the second one because it explicitly says that you access the data through a class
68
Practical Laravel
We will use classic getters and setters for the rest of the application. We hope you understand their
importance now.
Execute in Terminal
php artisan serve
The application should be properly working. If you applied the modification in the getName method,
you would see all products’ names in uppercase (see Fig. 14-1).
69
Practical Laravel
To create the admin panel, we need to include a layout, a controller, a view, new files, and a new
route. So, let’s begin.
Admin Layout
Commonly, administration panels look different from the main pages. They are quite minimal and
many of them display information like a spreadsheet. Let’s start our admin panel construction by
defining a new layout. This layout will be used across the admin panel pages.
In resources/views/layouts, create a new file admin.blade.php and fill it with the following code.
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
crossorigin="anonymous" />
<link href="{{ asset('/css/admin.css') }}" rel="stylesheet" />
<title>@yield('title', 'Admin - Online Store')</title>
</head>
<body>
<div class="row g-0">
<!-- sidebar -->
<div class="p-3 col fixed text-white bg-dark">
<a href="{{ route('admin.home.index') }}" class="text-white text-decoration-none">
<span class="fs-4">Admin Panel</span>
</a>
<hr />
70
Practical Laravel
71
Practical Laravel
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous">
</script>
</body>
</html>
We have a new layout. This layout presents a different structure and contains a sidebar with three
links.
• “- Admin - Home” links to the admin panel home page.
• “- Admin - Products” links to the admin panel product management page (this page will be
implemented in the next chapter).
• “Go back to the home page” links back to the online store home page.
The admin.blade.php layout imports an admin.css file and a new image. Let’s add these elements to
our project.
In public/css, create a new file admin.css and fill it with the following code.
.fixed {
-ms-flex: 0 0 250px;
flex: 0 0 250px;
}
.content-grey {
background-color: #f8f9fc;
}
hr {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
72
Practical Laravel
.img-profile {
height: 2rem;
width: 2rem;
}
.profile-font {
color: #858796 !important;
font-size: 80%;
font-weight: 400;
}
.card-header {
background-color: #f8f9fc;
border-bottom: 1px solid #e3e6f0;
}
We have some custom CSS classes. Most of them are used in the admin layout.
AdminHomeController
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
73
Practical Laravel
{
$viewData = [];
$viewData["title"] = "Admin Page - Admin - Online Store";
return view('admin.home.index')->with("viewData", $viewData);
}
}
Let’s create the admin index view. In resources/views/, create a subfolder admin. Next, create a
subfolder home in resources/views/admin. Finally, in resources/views/admin/home, create a new file
index.blade.php and fill it with the following code.
We have a simple view that displays a “welcome to the admin panel” message. Note that this view
extends the new admin layout (not the app layout).
Modifying routes
Let’s include a new route to the admin home page. In routes/web.php, make the following changes
in bold.
74
Practical Laravel
Route::get('/admin', 'App\Http\Controllers\Admin\AdminHomeController@index')-
>name("admin.home.index");
The new route (“/admin”) will display the new admin home page (which is rendered inside the
AdminHomeController index method).
Execute in Terminal
php artisan serve
Now go to the (“/admin”) route, and you will see the new Admin Panel (see Fig. 15-1).
Note: you can find the application code at the GitHub repository in
https://github.com/PracticalBooks/Practical-Laravel
75
Practical Laravel
Modifying routes
Let’s include a new route to the admin product index page. In routes/web.php, make the following
changes in bold.
The new route (“/admin/products”) will be the entry point to manage our products.
AdminProductController
namespace App\Http\Controllers\Admin;
use App\Models\Product;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
76
Practical Laravel
We have an index method that collects the products data and displays the admin.products.index
view.
77
Practical Laravel
</tbody>
</table>
</div>
</div>
@endsection
We have a table which displays the products’ ids and names. This table was designed based on
Bootstrap Tables (https://getbootstrap.com/docs/5.1/content/tables/). Later, we will include a link
to edit and delete specific products.
Now that we have the proper admin product route, let’s include it in the admin layout. In
resources/views/layouts/admin.blade.php, make the following changes in bold.
Execute in Terminal
php artisan serve
78
Practical Laravel
Now go to the (“/admin/products”) route, and you will see the new Admin Product page (see Fig.
16-1). Remember that you will need to have MySQL running. Otherwise, you will get a database
connection error.
79
Practical Laravel
Modifying routes
The new route (“/admin/products/store”) will collect and store the newly created products’ data. It
uses a post HTTP method since the controller method will collect information for a form.
80
Practical Laravel
@csrf
<div class="row">
<div class="col">
<div class="mb-3 row">
<label class="col-lg-2 col-md-6 col-sm-12 col-form-label">Name:</label>
<div class="col-lg-10 col-md-6 col-sm-12">
<input name="name" value="{{ old('name') }}" type="text" class="form-control">
</div>
</div>
</div>
<div class="col">
<div class="mb-3 row">
<label class="col-lg-2 col-md-6 col-sm-12 col-form-label">Price:</label>
<div class="col-lg-10 col-md-6 col-sm-12">
<input name="price" value="{{ old('price') }}" type="number" class="form-control">
</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="description" rows="3">{{ old('description') }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
Manage Products
...
Analyze Code
@if($errors->any())
<ul class="alert alert-danger list-unstyled">
@foreach($errors->all() as $error)
81
Practical Laravel
Laravel provides an $errors variable which is available in all views. This variable allows access to
errors reported by the application. For example, errors are found when submitting a form with
incomplete or invalid inputs. In the previous code, we are displaying all those errors.
Analyze Code
<form method="POST" action="{{ route('admin.product.store') }}">
@csrf
…
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Next, we have an HTML form. This form specifies a POST method and links the form with the
admin.product.store route (the route that was defined in the previous section). POST method is used
to send data to the application server. Laravel requires to include a hidden CSRF token field for all
our HTML forms. That token protects us against CSRF attacks (more info here:
https://owasp.org/www-community/attacks/csrf). To create that token, we use the @csrf Blade
directive. Finally, we have the submit button to submit the form.
Analyze Code
<label class="col-lg-2 col-md-6 col-sm-12 col-form-label">Name:</label>
<div class="col-lg-10 col-md-6 col-sm-12">
<input name="name" value="{{ old('name') }}" type="text" class="form-control">
</div>
The rest of the code shows form inputs, buttons, and a text area. This form and elements were
designed based on Bootstrap Forms (https://getbootstrap.com/docs/5.1/forms/overview/). They
show an input to enter the product name. We are also using as the input value this code value="{{
old('name') }}". The old Laravel helper is used to repopulate a previously collected input value if we
find some errors.
Note: we are not yet collecting the product image. We will do so in the next chapter.
Modifying AdminProductController
82
Practical Laravel
return back();
}
}
Analyze Code
public function store(Request $request)
{
$request->validate([
"name" => "required|max:255",
"description" => "required",
"price" => "required|numeric|gt:0",
'image' => 'image',
]);
83
Practical Laravel
store function receives a $request object. This object allows us to interact with the HTTP request
handled by our application. It also allows us to retrieve the inputs, cookies, and files submitted with
the request.
Next, we use the validate method provided by the $request object. If the validation rules pass, the
code will keep executing normally. However, an exception will be thrown if validation fails, and the
proper error response will automatically be sent back to the user. Those errors will be displayed in
the product/index.blade.php view through the $errors global variable.
In our case, name and description are required, and the name has a maximum length of 255
characters. Besides, price is required, must be numeric and greater than zero. Then, we are
validating the image input contains only image files (this will be used in the next chapter). You can
find the available validation rules here: https://laravel.com/docs/9.x/validation#available-
validation-rules.
Analyze Code
$newProduct = new Product();
$newProduct->setName($request->input('name'));
$newProduct->setDescription($request->input('description'));
$newProduct->setPrice($request->input('price'));
$newProduct->setImage("game.png");
$newProduct->save();
Next, we create a newProduct instance. Then, we set the newProduct attributes based on values
collected from the form. We use the request->input method to retrieve the form inputs. We also set
a default image (game.png). In the end, we invoke the save method, which inserts the object data
into the database. The model's created_at and updated_at timestamps will automatically be set
when the save method is called, so there is no need to set them manually.
Note: setting one by one each model attribute presents an issue. When a model has many
attributes, it implies manually calling several setters. We will see an alternative at the end of this
chapter.
Analyze Code
return back();
Finally, we invoke the back helper redirecting the user to the previous location.
TIP: If you have required inputs, include the required attribute to the HTML inputs
(https://www.w3schools.com/tags/att_input_required.asp). This way, we can also
validate those inputs using the client browser. We didn’t implement them because
we wanted to show how the Laravel validations work. But you should refactor all
required inputs with the previous strategy.
84
Practical Laravel
Execute in Terminal
php artisan serve
Now go to the (“/admin/products”) route, and you will see the new form. First, complete the form
with invalid data (see Fig. 17-1). Then, when you complete and submit the form with valid data (see
Fig. 17-2), you will see the new product listed (see Fig. 17-3).
85
Practical Laravel
There is another product store alternative. We will not codify this alternative into the application.
If you want to apply this alternative, we suggest you make an entire copy of the original project and
apply the next changes over the project copy.
Analyze Code
public function store(Request $request)
{
$request->validate([
"name" => "required|max:255",
"description" => "required",
"price" => "required|numeric|gt:0",
'image' => 'image',
]);
$creationData = $request->only(["name","description","price"]);
$creationData["image"] = "game.png";
Product::create($creationData);
86
Practical Laravel
return back();
}
As you can see, we removed the product instance creation and the setters. Instead of that, we
created an associative array called creationData with the name, description, and price collected from
the form (through the $request->only method). Then, we added the image data to the array with a
default image value (game.png). And, we invoked the Product::create method. To which we passed
the creationData array, which contains the product data. The create method (which accepts an array
of attributes) creates a model and inserts it into the database.
The advantage of this option is that we don’t need to assign one by one each attribute. However,
the disadvantage of this option is that it never invokes the classic setters that we defined in the
Product model. Sometimes, setters are helpful to add functionalities to modify the way we want to
store the model attributes.
Note: if you want to implement the previous strategy, you will also need to modify the Product
model. You will need to include the following code in the app/Models/Product.php file. Remember,
we will not codify this alternative into the application.
Analyze Code
…
class Product extends Model
{
...
protected $fillable = [
'name',
'description',
'price',
'image',
];
Laravel protects us against mass assignment vulnerabilities. By default, we cannot create a new
product by invoking the create method and passing an array with multiple data that refers to our
model attributes. To deal with this problem, we must define an attribute called fillable in our model.
Then, we specify which specific attributes can be assigned by the create method to create a new
87
Practical Laravel
product. In this case, we will allow assigning name, description, price, and image data to create new
products.
Note for advanced Laravel developers: there is an alternative to continue using the Product::create
method, which at the same time invokes our classic setters. This option requires to create a “PHP
trait” that overrides two Laravel methods, hasSetMutator and setMutatedAttributeValue and
requires the inclusion of that trait in the models. Here is the repository link with that custom
solution: https://github.com/PracticalBooks/Practical-Laravel/tree/main/Chapter17-
Advanced/onlineStore and here you can find the specific code changes:
https://github.com/PracticalBooks/Practical-
Laravel/commit/b32a66463cbbd95789ebc17cf04f46a40ccc9ee7. We won’t explain this strategy in
detail. However, if you have any questions or a better solution, remember to use the discussion
zone of the book repository.
88
Practical Laravel
Laravel Storage
Laravel provides a powerful filesystem abstraction thanks to the Flysystem PHP package by Frank
de Jonge. The Laravel Flysystem integration provides simple drivers for working with local
filesystems, SFTP, and Amazon S3 (https://laravel.com/docs/9.x/filesystem).
Laravel provides a class called Storage. This class contains a set of methods which allow creating,
deleting, and moving files and directories. It also allows defining the kind of disk we want to interact
with (i.e., local disk or Amazon S3).
Let’s modify our admin product form to include selecting a product image, and later we will use the
Laravel Storage class to store our images.
89
Practical Laravel
<div class="col">
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
...
Modifying AdminProductController
if ($request->hasFile('image')) {
$imageName = $newProduct->getId().".".$request->file('image')->extension();
Storage::disk('public')->put(
$imageName,
file_get_contents($request->file('image')->getRealPath())
);
$newProduct->setImage($imageName);
$newProduct->save();
}
90
Practical Laravel
return back();
}
}
We include the Laravel Storage library. Then, we check if an image was uploaded by using the
$request->hasFile method. If an image was uploaded, we get the newProduct id and use it as a base
to define the imageName. For example, if we create a new product and it is inserted with the id 8,
and we also upload an image for that product. Then, we will update the product image value with
the product id, plus a dot, plus the image extension (i.e., 8.png).
We also set the Storage with a public disk. This disk is intended for files that are going to be publicly
accessible (as our product images). The public disk stores files in storage/app/public folder by
default. The put method will store our product images over the public disk.
To make these files accessible from the web, we must create a “symbolic link” from public/storage
to storage/app/public. Then, in the Terminal, go to the project directory, and execute the following:
Execute in Terminal
php artisan storage:link
Then, you will see something like the result in Fig. 18-1. We need to create this “symbolic link”
because once we run our application (with php artisan serve), the users only can access files located
inside public/ folder. The rest of folders and files cannot be accessed.
91
Practical Laravel
<div class="col-md-4">
<img src="{{ asset('/storage/'.$viewData["product"]->getImage()) }}" class="img-fluid rounded-
start">
</div>
...
We are accessing the product images through the storage folder path.
Execute in Terminal
php artisan serve
Go to the (“/admin/products”) route, and you will see the new form (see Fig. 18-2). Then, complete
the form, upload an image, and create a new product.
Figure 18-2. Online Store – Create products form with image selection.
Access to the (“/products”) route. You will see the new product with its corresponding image.
However, the old product images are not loading (see Fig. 18-3). It is because we changed the path
from which we are loading the images. Don’t worry. In the next chapter, we will implement a way
to edit our products and upload proper images for the rest of our products.
92
Practical Laravel
Figure 18-3. Online Store – List products with their uploaded images.
93
Practical Laravel
Deleting products
Modifying AdminProductController
We include a new delete method at the end of the AdminProductController class. This method
invokes the model destroy method, which takes a primary key (id) as a parameter and deletes the
corresponding object from the database. In the end, we redirect to the previous user location.
Modifying routes
94
Practical Laravel
Modifying layout/admin
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"
crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-
icons.css">
...
We add a link to a CSS file (Bootstrap icons). This CSS file allows us to use specific icons and fonts
inside our views. For example, we will have icons to edit and delete our products. More information
about Bootstrap icons can be found at: https://icons.getbootstrap.com/.
95
Practical Laravel
We removed the “Edit” and “Delete” text and replaced them with a couple of buttons. Inside those
buttons, we use Bootstrap icons (a pencil icon to edit a product and a trash icon to delete a product).
We completed the delete functionality by wrapping a form around the delete icon. This form will
invoke the admin.product.delete route, and will pass the current product id. Check that we use the
@method('DELETE') Blade directive since the route uses the delete method.
Execute in Terminal
php artisan serve
Now go to the (“/admin/products”) route, and you will see the new form (see Fig. 19-1). Now, try
to delete a product.
96
Practical Laravel
Editing products
Like the previous delete functionality, we will need to modify a set of files.
Modifying AdminProductController
$product = Product::findOrFail($id);
97
Practical Laravel
$product->setName($request->input('name'));
$product->setDescription($request->input('description'));
$product->setPrice($request->input('price'));
if ($request->hasFile('image')) {
$imageName = $product->getId().".".$request->file('image')->extension();
Storage::disk('public')->put(
$imageName,
file_get_contents($request->file('image')->getRealPath())
);
$product->setImage($imageName);
}
$product->save();
return redirect()->route('admin.product.index');
}
}
Analyze Code
public function edit($id)
{
$viewData = [];
$viewData["title"] = "Admin Page - Edit Product - Online Store";
$viewData["product"] = Product::findOrFail($id);
return view('admin.product.edit')->with("viewData", $viewData);
}
We have an edit method that searches for a product based on its id, and sends it to the
admin.product.edit view. It is the product we are going to edit.
Analyze Code
public function update(Request $request, $id)
{
$request->validate([
"name" => "required|max:255",
"description" => "required",
"price" => "required|numeric|gt:0",
98
Practical Laravel
$product = Product::findOrFail($id);
$product->setName($request->input('name'));
$product->setDescription($request->input('description'));
$product->setPrice($request->input('price'));
if ($request->hasFile('image')) {
$imageName = $product->getId().".".$request->file('image')->extension();
Storage::disk('public')->put(
$imageName,
file_get_contents($request->file('image')->getRealPath())
);
$product->setImage($imageName);
}
$product->save();
return redirect()->route('admin.product.index');
}
Modifying routes
99
Practical Laravel
Route::put('/admin/products/{id}/update',
'App\Http\Controllers\Admin\AdminProductController@update')->name("admin.product.update");
We have two new routes. The (“/admin/products/{id}/edit”) will be used to show a form in which
we enter the new product data. It uses a get HTTP method since the controller method will show a
form to edit a product. The (“/admin/products/{id}/update”) will be used to update the product
data. It uses a put HTTP method since the controller method will update product data.
Now, we link the pencil icon with the admin.product.edit route and pass in the current product id.
In resources/views/admin/product, create a new file edit.blade.php and fill it with the following code.
100
Practical Laravel
<div class="card-header">
Edit Product
</div>
<div class="card-body">
@if($errors->any())
<ul class="alert alert-danger list-unstyled">
@foreach($errors->all() as $error)
<li>- {{ $error }}</li>
@endforeach
</ul>
@endif
101
Practical Laravel
<div class="row">
<div class="col">
<div class="mb-3 row">
<label class="col-lg-2 col-md-6 col-sm-12 col-form-label">Image:</label>
<div class="col-lg-10 col-md-6 col-sm-12">
<input class="form-control" type="file" name="image">
</div>
</div>
</div>
<div class="col">
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="description"
rows="3">{{ $viewData['product']->getDescription() }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Edit</button>
</form>
</div>
</div>
@endsection
The previous view is like the form created in the admin/product/index view but with minor
differences. We use the PUT method (since we are updating a resource) and populate the input
values with the product attributes.
Execute in Terminal
php artisan serve
Go to the (“/admin/products”) route, click an edit button of a specific product, and you will see the
edit form (see Fig. 19-2). Now, you can edit products. We suggest editing all products to replace the
missing images (see Fig. 19-3).
102
Practical Laravel
103
Practical Laravel
There are multiple strategies to deal with model validations. For example, you can use the request-
>validate method, create separated Laravel Form Requests classes, and create custom Validators
(https://laravel.com/docs/9.x/validation). We will implement a simple strategy. It will work well for
our needs, and indeed, much better than the strategy of the previous chapter. We will move model
validations to the corresponding model files and invoke those validations from the controllers. Let’s
see this strategy in action.
For now, let’s refactor our Product model. In app/Models/Product.php, make the following changes
in bold.
104
Practical Laravel
We moved the duplicated controller validations to a single place. We placed them inside a static
method called validate inside our Product model class. Now, we have a unique representation of
our Product validations, which will improve our code maintainability.
TIP: Always try to move your model validations away from the controllers. You can
place them in methods inside your models or into separated classes. Even you can
include a new rule (in your architectural rules document) saying that models’
validations must be done in a model method called validate or in the location of your
preference (but not in controllers).
Refactoring AdminProductController
105
Practical Laravel
]);
...
}
...
We removed the previous validation logic, which was inside our store and update methods. Instead
of that, now we invoke the validate method of our Product model.
Finally, you can run the application and check that the validations are working as expected.
106
Practical Laravel
Laravel 6.* and 7.* provided an official authentication system called laravel/ui
(https://laravel.com/docs/7.x/authentication). laravel/ui is a straightforward authentication system
built on the Bootstrap CSS framework (https://github.com/laravel/ui). This library was created by
Taylor Otwell (the creator of Laravel). Later, this library provided support to create an authentication
system with Vue and React. However, laravel/ui is no longer the official authentication system for
Laravel 8.* and 9.*.
If we want to use some of the three previous options to implement the authentication system, we
will need to use additional CSS frameworks or JavaScript frameworks (such as React or Vue) which
is out of the scope of this book. Fortunately, laravel/ui continues to be available and supports for
Laravel 9.* applications.
Installing laravel/ui
Execute in Terminal
composer require laravel/ui
The previous command includes the laravel/ui library over our composer.json file. And it installs that
library in our vendor folder.
We also need to generate the frontend scaffolding and the login system. In the Terminal, go to the
project directory, and execute the following:
107
Practical Laravel
Execute in Terminal
php artisan ui bootstrap --auth
You will be asked to replace the app layout and the HomeController, type “no” to both questions
(see Fig. 21-1).
laravel/ui creates:
• The app/Http/Controllers/Auth folder that includes some authentication controllers such as
LoginController and RegisterController.
• The resources/views/auth folder that includes some authentication views such as login and
register. Those views are generated with simple HTML and Bootstrap code.
• A modification over the web.php file that includes the Auth routes and a /home route.
Let’s make some changes to finalize the inclusion of the authentication system over our Online Store
application.
Modifying web.php
In routes/web.php, remove the following line in bold (at the end of the file).
Auth::routes();
laravel/ui creates a default (“/home”) route. However, our application does not have a (“/home”)
route, so we remove it.
Modifying RouteServiceProvider
In app/Providers/RouteServiceProvider.php, we replace the HOME attribute for the next one in bold.
108
Practical Laravel
The value of the HOME attribute is used by Laravel auth to redirect users after login. Since we don’t
have a (“/home”) route, we replace it with the main route (“/”).
Modifying app.blade.php
Blade provides @auth and @guest directives to determine if the current user is authenticated
(@auth) or is a guest (@guest). If the user is a guest, we will show the register and login links. On
the other hand, if the user is authenticated, we will show the logout link. All these links are
connected to auth routes.
109
Practical Laravel
@if (Route::has('password.request'))
<a class="btn bg-primary text-white" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
…
Execute in Terminal
php artisan serve
110
Practical Laravel
Go to the (“/register”) route, you will see the registration form and the new links over the navigation
bar (see Fig. 21-2). First, create a new user. Then, you will be redirected to the home page as an
authenticated user. Then, you will see the logout link (see Fig. 21-3). Finally, you can click the logout
link and test the login system with your user credentials (see Fig. 21-4).
111
Practical Laravel
Users are registered in the users table. This table was created the first time we ran Laravel
Migrations. Laravel also includes a User model. We will analyze this model in the next chapter.
Note: you can find the application code at the GitHub repository in
https://github.com/PracticalBooks/Practical-Laravel.
112
Practical Laravel
Let’s create a migration to add two columns to the users table. In the Terminal, go to the project
directory, and execute the following:
Execute in Terminal
php artisan make:migration alter_users_table
The previous command creates a new migration inside the database/migrations folder. Open the
generated file (something like 2022_02_12_140820_alter_users_table.php) and replace the up and
down methods with the following.
113
Practical Laravel
The up method adds two columns to the users table. The role column will be used to know if a user
is an admin or client. client will be the default value. admin users will be able to access the admin
panel. In the balance column, we will store the user’s balance. The user’s balance represents the
user’s money inside the application (we will use it for the purchase functionality).
Let’s execute the previous migration, go to the project directory, and in the Terminal, execute the
following command (see Fig. 22-1).
Execute in Terminal
php artisan migrate
Let’s refactor the default Laravel User model. In app/Models/User.php, make the following changes
in bold.
/**
* USER ATTRIBUTES
* $this->attributes['id'] - int - contains the user primary key (id)
* $this->attributes['name'] - string - contains the user name
* $this->attributes['email'] - string - contains the user email
* $this->attributes['email_verified_at'] - timestamp - contains the user email verification date
* $this->attributes['password'] - string - contains the user password
* $this->attributes['remember_token'] - string - contains the user password
* $this->attributes['role'] - string - contains the user role (client or admin)
* $this->attributes['balance'] - int - contains the user balance
* $this->attributes['created_at'] - timestamp - contains the user creation date
* $this->attributes['updated_at'] - timestamp - contains the user update date
114
Practical Laravel
*/
...
protected $fillable = [
'name',
'email',
'password',
'balance',
];
...
115
Practical Laravel
116
Practical Laravel
{
return $this->attributes['created_at'];
}
We included the User attributes block of comments and added some getters and setters. We also
added the balance in the fillable attribute.
TIP: We made some improvements to the User model. Now, a developer can know
the available User attributes. In addition, we leave the code a little cleaner than we
found it. The Boy Scouts of America have a simple rule that we can apply to our
profession. Leave the campground cleaner than you found it (2008 - Martin, R. C.-
Clean Code).
<?php
...
class RegisterController extends Controller
{
117
Practical Laravel
...
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'balance' => 5000,
]);
}
...
We modified the way users are created. When a new user is created, we will set the user’s balance
to 5000. It is just an example. Later, you can improve this application to connect with a payment
system and properly update the user’s balance.
Execute in Terminal
php artisan serve
Now go to the (“/register”) route and create a new user (remember you must be logged out to
access the “/register” route). You will see in the database that the new user has 5000 in balance and
a client role (see Fig. 22-2).
Defining admins
If you check your users table, you will see that all users are clients. We don’t have admins. Here we
have two options.
• Go to phpMyAdmin, click the online_store database, click the users table, browse a specific
user, and edit the user’s role (change it from client to admin). Check Fig. 22-3.
• Use Laravel Tinker and create a user from scratch.
118
Practical Laravel
Laravel Tinker
Laravel Tinker is a powerful REPL (Read–Eval–Print Loop) for the Laravel framework
(https://laravel.com/docs/9.x/artisan#tinker). Tinker allows you to interact with your Laravel
application on the command line.
Let’s use Laravel Tinker. In the Terminal, go to the project directory, and execute the following:
Execute in Terminal
php artisan tinker
You will see a command line where we will create an admin user. Type the following commands,
and you will get a result like in Fig. 22-4.
Execute in Terminal
$user = new App\Models\User();
$user->setName('Daniel');
$user->setEmail('daniel@danielgara.com');
$user->setPassword(bcrypt('passwordVerySecret'));
$user->setBalance(5000);
$user->setRole('admin');
$user->save();
exit;
119
Practical Laravel
We created a new user, hashed the password with bcrypt() for security, and saved the user to the
database. Now, you can log in to the application with the previous credentials.
TIP: You can use Tinker to make quick verifications. For example, in Tinker, you can
enter the following command ($products = App\Models\Product::all();) to check the
stored products in the database.
120
Practical Laravel
Chapter 23 – AdminAuthMiddleware
Let’s restrict the access to the admin panel just for admins. Laravel provides a set of options to
operationalize this functionality. A couple of them are called gates and policies
(https://laravel.com/docs/9.x/authorization). We won’t use them. Instead, we will use another
approach, a Laravel Middleware.
Laravel Middleware
Laravel middleware provides a convenient mechanism for inspecting and filtering HTTP requests
entering your application. For example, Laravel includes a middleware that verifies your
application’s user is authenticated. If the user is not authenticated, the middleware will redirect the
user to your application's login screen. However, if the user is authenticated, the middleware will
allow the request to proceed further into the application. Additional middleware can be written to
perform a set of tasks besides authentication. For example, a logging middleware might log all
incoming requests to your application.
We will create a middleware to verify that only admin users can access the admin panel. Otherwise,
we will redirect them to the home page.
AdminAuthMiddleware
To create a new middleware, in the Terminal, go to the project directory, and execute the following:
Execute in Terminal
php artisan make:middleware AdminAuthMiddleware
class AdminAuthMiddleware
{
...
public function handle(Request $request, Closure $next)
{
121
Practical Laravel
The middleware checks if the user is authenticated and is an admin. If that is true, the application
continues its normal execution flow. Otherwise, we redirect to the home.index route.
Registering AdminAuthMiddleware
We will use AdminAuthMiddleware to restrict access to admin routes. So, we will need to register
the middleware in our application's app/Http/Kernel.php file. In app/Http/Kernel.php, make the
following changes in bold.
Modifying web.php
Let’s connect the previous middleware with the routes we want to restrict. In routes/web.php, make
the following changes in bold.
122
Practical Laravel
Route::middleware('admin')->group(function () {
Route::get('/admin', 'App\Http\Controllers\Admin\AdminHomeController@index')-
>name("admin.home.index");
Route::get('/admin/products', 'App\Http\Controllers\Admin\AdminProductController@index')-
>name("admin.product.index");
Route::post('/admin/products/store',
'App\Http\Controllers\Admin\AdminProductController@store')->name("admin.product.store");
Route::delete('/admin/products/{id}/delete',
'App\Http\Controllers\Admin\AdminProductController@delete')->name("admin.product.delete");
Route::get('/admin/products/{id}/edit',
'App\Http\Controllers\Admin\AdminProductController@edit')->name("admin.product.edit");
Route::put('/admin/products/{id}/update',
'App\Http\Controllers\Admin\AdminProductController@update')->name("admin.product.update");
});
Auth::routes();
We grouped all (“/admin/*”) routes around the new middleware. It means that any guest or client
user who tries to access any (“/admin/*”) route will be redirected to the home page. Only admin
users can access those routes.
Execute in Terminal
php artisan serve
Now go to the (“/admin”) as a guest or client user, and you will be redirected to the home page.
123
Practical Laravel
In this chapter, we will try to solve the previous questions and understand why web session is
important in the construction of web applications.
For now, we have relied on the HTTP protocol to communicate with our Online Store. For example,
if we want to get products information, we access http://127.0.0.1:8000/products. To log in, we
access http://127.0.0.1:8000/login. Each of our requests uses the HTTP protocol as a means of
communication.
However, the HTTP protocol has some limitations. HTTP is a stateless protocol, meaning that the
server does not keep any data (state) between two requests. In HTTP, every request creates a new
connection, and each new request knows nothing about any prior requests. That is, it holds no state
data.
But if you remember, when you are logged into the application, the following requests will show
you the logout button (which means that the application is using a mechanism to identify you) to
keep state data. It is because Laravel increases the HTTP protocol capabilities using Laravel sessions.
Laravel sessions
Laravel sessions provide a way to store information about the user across multiple requests. User
information is typically placed in a persistent store that can be accessed from subsequent requests.
By default, Laravel uses a file session driver. This driver stores the session data in files inside the
storage/framework/sessions folder.
Web sessions
A web session is a series of contiguous actions by a visitor on a website within a given time frame.
Each framework provides its mechanism to implement sessions (to track the visitors’ actions). Figure
24-1 shows how Laravel sessions work.
124
Practical Laravel
125
Practical Laravel
We have seen how the Laravel session works. But sessions are not only used for login systems.
Sessions can also store flash messages, CSRF tokens, the last page visited by the user, and even
products added to a cart. In addition, you can use sessions to store temporary data that is useful to
track the current user. Data that you don’t need to persist permanently. If you need to store data
permanently, you should use Laravel Eloquent instead of Laravel sessions.
In the next chapter, we will see how to use Laravel sessions to design a shopping cart.
126
Practical Laravel
CartController
In app/Http/Controllers, create a new file CartController.php and fill it with the following code.
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
$productsInSession = $request->session()->get("products");
if ($productsInSession) {
$productsInCart = Product::findMany(array_keys($productsInSession));
$total = Product::sumPricesByQuantities($productsInCart, $productsInSession);
}
$viewData = [];
$viewData["title"] = "Cart - Online Store";
$viewData["subtitle"] = "Shopping Cart";
$viewData["total"] = $total;
$viewData["products"] = $productsInCart;
return view('cart.index')->with("viewData", $viewData);
}
127
Practical Laravel
{
$products = $request->session()->get("products");
$products[$id] = $request->input('quantity');
$request->session()->put('products', $products);
return redirect()->route('cart.index');
}
Analyze Code
public function add(Request $request, $id)
{
$products = $request->session()->get("products");
$products[$id] = $request->input('quantity');
$request->session()->put('products', $products);
return redirect()->route('cart.index');
}
The add method receives the request (which receives the quantity of product) and the product id
(the id of the product to be added to the cart). Then, we get the products stored in the session
through the request->session()->get("products") method. The first time, request->session()-
>get("products") won’t exist, so we assign it to an empty object. Next, we include in products variable
the collected product id with its quantity (id as key, quantity as value). We then update the products
stored in the session (with the use of the request->session()->put method). Finally, we redirect the
user to the cart.index route.
Analyze Code
public function delete(Request $request)
{
$request->session()->forget('products');
return back();
128
Practical Laravel
The delete method receives the request and removes the products stored in the session for that
request (using the request->session()->forget method). Then, we return to the previous route.
Analyze Code
public function index(Request $request)
{
$total = 0;
$productsInCart = [];
$productsInSession = $request->session()->get("products");
if ($productsInSession) {
$productsInCart = Product::findMany(array_keys($productsInSession));
$total = Product::sumPricesByQuantities($productsInCart, $productsInSession);
}
$viewData = [];
$viewData["title"] = "Cart - Online Store";
$viewData["subtitle"] = "Shopping Cart";
$viewData["total"] = $total;
$viewData["products"] = $productsInCart;
return view('cart.index')->with("viewData", $viewData);
}
The index method defines a total variable with a zero value and an empty productsInCart array. First,
we check if the current request has products stored in session. If there are productsInSession, we
extract the related products from the database. In this case, we use the model findMany method,
which receives an array with primary keys and returns a collection of objects. We send
array_keys($productsInSession) to this method, remember we store the products id as keys and the
quantities as values. Then, we update the total value by invoking the Product::sumPricesByQuantities
method (which will be implemented next). Finally, we send the total and products to the cart.index
view.
129
Practical Laravel
return $total;
}
Modifying web.php
Route::get('/cart', 'App\Http\Controllers\CartController@index')->name("cart.index");
Route::get('/cart/delete', 'App\Http\Controllers\CartController@delete')->name("cart.delete");
Route::post('/cart/add/{id}', 'App\Http\Controllers\CartController@add')->name("cart.add");
We included three new routes which are linked to the corresponding CartController methods.
Modifying app.blade.php
130
Practical Laravel
131
Practical Laravel
</p>
</div>
...
We add a new form where the user enters the product’s quantity to the cart. This form is linked to
the cart.add route.
132
Practical Laravel
@endforeach
</tbody>
</table>
<div class="row">
<div class="text-end">
<a class="btn btn-outline-secondary mb-2"><b>Total to pay:</b> ${{ $viewData["total"] }}</a>
<a class="btn bg-primary text-white mb-2">Purchase</a>
<a href="{{ route('cart.delete') }}">
<button class="btn btn-danger mb-2">
Remove all products from Cart
</button>
</a>
</div>
</div>
</div>
</div>
@endsection
This view is like the admin/product/index view. We iterate and display the products added in session.
We also use the session global helper to access the products’ quantities. We then display the total
to be paid, a purchase button (which doesn’t do anything yet), and a button to remove all products
from the cart linking the cart.delete route.
Execute in Terminal
php artisan serve
Go to the (“/products”) route, click a specific product, and you will see the new product/show view
(see Fig. 25-1). Next, you can add some products to the cart and visit the (“/cart”) route. It will show
you the total to be paid and a button to remove all products from the cart (see Fig. 25-2).
133
Practical Laravel
134
Practical Laravel
Let’s take Fig. 26-1 as a base. Though a simplified invoice, it is helpful to show the data we need to
store.
Orders are composed of items (see the internal table in Fig. 26-1). So, for each Item, we need to
store:
• Quantity: in the example, it is 1 for the first item. It means that the user is buying one TV.
• Product id: in the example, it is 1 for the first item. It means that the user is buying the product
with id 1.
• Product name: in the example, it is TV for the first item. Product name won’t be stored since
we can retrieve it based on product id.
• Price: in the example, it is 1000 for the first item. We will store this price since it is common
for products to change their price. Also, storing the price in the item table will allow us to
know at which price the user bought each product.
• Id: we will include the item id to trace each item.
Now that we understand how these simplified invoices work, let’s create the proper models and
migrations.
135
Practical Laravel
Order migration
Let’s create the order migration. In the Terminal, go to the project directory, and execute the
following:
Execute in Terminal
php artisan make:migration create_orders_table
The previous command creates a new migration inside the database/migrations folder. Open the
generated file (something like 2022_02_12_152713_create_orders_table.php) and replace the up
and down methods with the following.
The up method will create a new database table called orders. The orders table will contain id, total,
user_id, and the corresponding timestamps. Note that we create a foreign key for the user_id
column, which references the id column of the users table. More information about foreign key
constraints can be found at: https://laravel.com/docs/9.x/migrations#foreign-key-constraints.
136
Practical Laravel
Item migration
Let’s create the item migration. In the Terminal, go to the project directory, and execute the
following:
Execute in Terminal
php artisan make:migration create_items_table
The previous command creates a new migration inside the database/migrations folder. Open the
generated file (something like 2022_02_12_152850_create_items_table.php) and replace the up
and down methods with the following.
137
Practical Laravel
The up method will create a new database table called items. The items table will contain id,
quantity, price, order_id, user_id, and the corresponding timestamps. Note that we create a foreign
key for the order_id column, which references the id column of the orders table. And a foreign key
for the product_id column, which references the id column of the products table.
Let’s execute the previous two migrations. Go to the project directory, and in the Terminal, execute
the following command (see Fig. 26-2).
Execute in Terminal
php artisan migrate
Order model
In app/Models, create a new file Order.php and fill it with the following code.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
use App\Models\Item;
138
Practical Laravel
139
Practical Laravel
{
$this->attributes['user_id'] = $userId;
}
140
Practical Laravel
The Order model contains a block of comments with its attributes, getters and setters, and a
validation. However, we apply four new things.
• We use an additional Laravel validation called exists. It verifies that the user_id corresponds
to an existing user id.
• We create a method called user. This method represents an Eloquent relationship
(https://laravel.com/docs/9.x/eloquent-relationships). Defining relationships as methods
provides powerful method chaining and querying capabilities that we will use later. In this
case, we use the belongsTo method, which indicates that the Order belongs to a specific User.
Eloquent determines the foreign key name by examining the name of the relationship method
and suffixing the method name with _id. So, in this case, Eloquent assumes that the Order
model has a user_id column.
• We create a method called items. Again, this is an Eloquent relationship. We use the hasMany
method, which indicates that the Order is parent to one or more child Item models (this is
called One-To-Many relationship). Eloquent will automatically determine the proper foreign
key column for the Item model. By convention, Eloquent will take the "snake case" name of
the parent model and suffix it with _id. So, in this example, Eloquent will assume the foreign
key column on the Item model is order_id.
• We create the getters and setters for the Eloquent relationships. However, we are not
accessing these elements with the $this->attributes form. It is because, once the relationship
is defined, Eloquent creates dynamic attributes based on those relationships. So, to get the
user of a specific Order, we will need to use the dynamic attribute $this->user inside the Order
class.
141
Practical Laravel
Once both relationship methods have been defined, we can access the related collections by using
the getUser() method or getItems() method (Eloquent will provide dynamic attributes based on
those relationships). We will see an example later.
Item model
In app/Models, create a new file Item.php and fill it with the following code.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Order;
use App\Models\Product;
142
Practical Laravel
]);
}
143
Practical Laravel
144
Practical Laravel
{
return $this->belongsTo(Order::class);
}
The Item model contains a block of comments with its attributes, getters and setters, and a
validation. Like the previous one, we put some validations related to the Item foreign keys and two
Eloquent relationships to the Order and Product models.
145
Practical Laravel
We added the orders attribute. Now, we can access from a User model to its corresponding orders.
146
Practical Laravel
We added the items attribute. Now, we can access from a Product model to its corresponding items.
147
Practical Laravel
Fig. 26-3 shows our initial class diagram. We have implemented all the elements in that diagram.
• We implemented the migrations for all the classes.
• We implemented all the classes’ attributes.
• We implemented all the classes’ associations.
• We implemented all the classes’ getters and setters.
• We didn’t need to implement CRUD methods since we inherited them from the Eloquent
model class.
148
Practical Laravel
Modifying web.php
Route::middleware('auth')->group(function () {
Route::get('/cart/purchase', 'App\Http\Controllers\CartController@purchase')-
>name("cart.purchase");
});
Route::middleware('admin')->group(function () {
…
We create the (“cart/purchase”) route. This route will be only available for authenticated users.
149
Practical Laravel
</div>
</div>
</div>
@endsection
We modified the cart.index view. We only show the Purchase link and the “Remove all products
from Cart” button if the user has products in session. And we link the Purchase link with the
cart.purchase route.
In resources/views/cart, create a new file purchase.blade.php and fill it with the following code.
It is a simple view that displays a message and shows the order id. This view will be displayed when
the user completes the purchase.
Modifying ProductController
150
Practical Laravel
use App\Models\Product;
use App\Models\Order;
use App\Models\Item;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
$total = 0;
$productsInCart = Product::findMany(array_keys($productsInSession));
foreach ($productsInCart as $product) {
$quantity = $productsInSession[$product->getId()];
$item = new Item();
$item->setQuantity($quantity);
$item->setPrice($product->getPrice());
$item->setProductId($product->getId());
$item->setOrderId($order->getId());
$item->save();
$total = $total + ($product->getPrice()*$quantity);
}
$order->setTotal($total);
$order->save();
151
Practical Laravel
Auth::user()->save();
$request->session()->forget('products');
$viewData = [];
$viewData["title"] = "Purchase - Online Store";
$viewData["subtitle"] = "Purchase Status";
$viewData["order"] = $order;
return view('cart.purchase')->with("viewData", $viewData);
} else {
return redirect()->route('cart.index');
}
}
}
Analyze Code
public function purchase(Request $request)
{
$productsInSession = $request->session()->get("products");
if ($productsInSession) {
$userId = Auth::user()->getId();
$order = new Order();
$order->setUserId($userId);
$order->setTotal(0);
$order->save();
…
} else {
return redirect()->route('cart.index');
}
}
We define the purchase method that is the most complex in this book. In the beginning, we check if
the user has products in session. If there are no products in session, we redirect the user to the
cart.index route. If there are products, we create an Order with the logged user id and a purchase
total of 0 (we will update this value later). We create this Order because we need to access the Order
id to create items.
152
Practical Laravel
Analyze Code
$total = 0;
$productsInCart = Product::findMany(array_keys($productsInSession));
foreach ($productsInCart as $product) {
$quantity = $productsInSession[$product->getId()];
$item = new Item();
$item->setQuantity($quantity);
$item->setPrice($product->getPrice());
$item->setProductId($product->getId());
$item->setOrderId($order->getId());
$item->save();
$total = $total + ($product->getPrice()*$quantity);
}
Then, we iterate through the productsInCart. For each product in productsInCart, we create a new
Item, set the corresponding quantity (based on the values stored in session), price, product id, and
order id. We then save the item and update the total value.
Analyze Code
$order->setTotal($total);
$order->save();
$request->session()->forget('products');
$viewData = [];
$viewData["title"] = "Purchase - Online Store";
$viewData["subtitle"] = "Purchase Status";
$viewData["order"] = $order;
return view('cart.purchase')->with("viewData", $viewData);
We update the order total and save it. Then, we calculate and set the new user’s balance. We then
remove the products in session, and show the cart.purchase view with the order.
Note: we have not verified if the user has enough money to purchase. Try to include that validation
in the previous code. You can use the discussion zone of the book repository to show us your
solution.
153
Practical Laravel
Execute in Terminal
php artisan serve
Now, log in to the application, go to the (“/products”) route, and add some products to the cart (see
Fig. 27-1). Click the Purchase button, and the application will show a confirmation message (see Fig.
27-2). Next, you can open phpMyAdmin and check the orders table (see Fig 27-3), and items table
(see Fig. 27-4) contains the new data.
154
Practical Laravel
155
Practical Laravel
Modifying web.php
We create the (“my-account/orders”) route. This route will be only available for authenticated
users.
Modifying app.blade.php
MyAccountController
In app/Http/Controllers, create a new file MyAccountController.php and fill it with the following code.
156
Practical Laravel
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Order;
use Illuminate\Support\Facades\Auth;
The orders method collects the orders based on the authenticated user id and displays the
myaccount.orders view.
157
Practical Laravel
We display the user orders with their respective information. For this view, we don’t use the
@foreach Blade directive. Instead of that, we use the @forelse directive. @forelse allows defining a
@empty directive in which we display a message in case the user doesn’t have any orders.
158
Practical Laravel
If the user has no orders, we use the @empty directive to show a message saying, “Seems to be that
you have not purchased anything in our store”. Otherwise, we show each order’s id, creation date,
and total (using the respective getters). Then, we have a second @foreach. This time, we iterate
through the items of each order (using the getItems() method). We show each item’s id, price, and
quantity. Finally, we access the product properties of each item (using the getProduct() method) and
display the product name and link to the respective product page.
Note the way we are easily accessing the order relationships (i.e., $item->getProduct()->getName()).
That’s the power of Eloquent relationships. Now, we can navigate through the entire class diagram
just with some getters.
Let’s check another example to understand the power of the Eloquent relationships. Previously, we
purchased an iPhone product. Then, open Laravel Tinker in our Terminal (php artisan tinker) and
execute the following command.
Analyze Code
Product::where('name', 'iPhone')->first()->getItems()[0]->getOrder()->getUser()->getName();
1 We look for the first product, which contains “iPhone” as its name.
2 We obtain the items in which that product was purchased.
3 We access the first item.
4 We obtain the order of the previous item.
5 We obtain the user of the previous order.
6 We access the name of the previous user.
In summary, we are obtaining the user’s name who bought the first “iPhone” in our store. Fig. 28-1
shows this name. It was Daniel. Can you believe it?
Execute in Terminal
php artisan serve
Now, log in to the application, go to the (“/my-account/orders”) route, and you will see your orders
(see Fig. 28-2). If you have no orders (see Fig. 28-3), make a purchase, and return to the orders page.
159
Practical Laravel
The use of Eloquent relationships has a silent cost. The related models are "lazy loaded" when we
access Eloquent relationships as attributes. It means the relationship data is not loaded until you
first access the attribute. It presents an important issue. For example, suppose we have ten orders
on the orders page. In this case, Laravel will make one database query to extract the ten orders
(that’s not an issue). However, in our orders.blade.php we created a @forelse which iterates over
each order and extracts the corresponding items. Since Eloquent uses lazy loading, for each order,
Laravel will make an extra database query to collect the information of the items related to each
order (another ten queries). And, since we are extracting the product name of each item, it will
execute another set of queries (minimum another ten queries). So, in this scenario, we will need to
execute at least 21 queries to show the orders page (that’s crazy). Lazy loading affects our
application performance. Fortunately, Eloquent can reduce the number of queries and "eager load"
relationships using the with method (we will see it in action later).
160
Practical Laravel
Fig. 28-4 shows the debug of the orders page (with our current lazy loading scenario). We had three
orders for this user, and in this case, the debug bar shows that nine queries were executed.
Figure 28-4. Online Store – Orders page with debug bar and lazy loading.
Let’s improve our application code and use eager loading for the orders page. In
app/Http/Controllers/MyAccountController.php, make the following changes in bold.
161
Practical Laravel
The previous code specifies which relationships should be eager loaded using the with method. In
this case, we load the orders with their corresponding items and the products of the related items.
Let’s reload the page and check the new results (see Fig. 28-5).
Figure 28-5. Online Store – Orders page with debug bar and eager loading.
We reduced the number of queries to four. But that’s not the best part. Now, if we have ten or 50
orders, we will always execute just four queries because we loaded the relationships in advance
(eager loading).
TIP: Always try to install a debug bar or mechanism to check the executed queries
of each application section. Remember to use eager loading to reduce the quantity
of executed queries. If that doesn’t work, try to use a cache mechanism or another
solution.
162
Practical Laravel
We have finished our application. Congratulations! We hope you better understand our architecture
diagram and its elements (see Fig. 28-6).
What an exciting journey! We have completed our Online Store with an MVC architecture. Let’s
have a final tip and discussion, before deploying our application to the cloud.
163
Practical Laravel
TIP: A final quote from the (2019 - Thomas, D., & Hunt, A. - The Pragmatic
Programmer: your journey to mastery) book. “Critically Analyze What You Read and
Hear”. That video you saw on YouTube, that post you read in StackOverFlow, this
paragraph that you are reading in this book. Does it make sense? Is it a good
strategy? Does it apply to your context? Is it true?
Quick discussion: Have you seen the “Harry Potter and the Half-Blood Prince” film?
In the film, Harry Potter was enrolled in a potions class. The class had an “Advanced
Potion-Making” textbook that contained a variety of recipes for various potions.
Harry had not bought his own textbook. So, the potions professor loaned one of the
older books (left behind by previous students) to Harry. This book belonged to
Severus Snape, whose nickname was the "Half-Blood Prince". Snape improved many
of the potions by means of including procedures and scribbling notes in the margins.
Harry tried Snape's methods and achieved the best results in the class. We all should
be like the Half-Blood Prince, trying to critically analyze everything and make our
own judgements. Book authors make mistakes, experts make mistakes, we all make
mistakes. There is still room for improvement.
164
Practical Laravel
Introduction to Clever-Cloud
Clever Cloud is a ‘Platform as a Service’ company that helps developers deploy and run their apps
over the cloud. In addition, Clever Cloud offers free service trials, such as the MySQL service. We
will deploy, run, and monitor our MySQL databases and tables using this service.
Validate your email. After that, access https://console.clever-cloud.com/, click Personal space,
Create…, and click an add-on. After that, select MySQL, choose the DEV plan option (the first one),
which costs 0.00€, and click next. Then, put onlineStore as the add-one name, leave the region as it
appears, and click next (see Fig. 29-2). We have created our MySQL cloud service.
165
Practical Laravel
After the MySQL creation, we will see a dashboard (see Fig. 29-3), which contains our “Database
Credentials” and a link to “phpMyAdmin”. We will use this information next.
Note: if for any reason, Clever Cloud disables the DEV free plan, just create a new topic in the
discussion zone of the book repo, and we will make a tutorial with an alternative platform.
Let’s make a copy of our application code. Copy all the content of your onlineStore folder to a new
folder onlineStoreCloud (in a location of your choice).
166
Practical Laravel
To execute the migrations over our cloud database, we must modify the .env file (located at the
cloud project root folder).
Go to your onlineStoreCloud folder, and in the .env file, make the following changes in bold. Replace
the DATABASE_CREDENTIALS_HOST, DATABASE_CREDENTIALS_DATABASE_NAME,
DATABASE_CREDENTIALS_USER, and DATABASE_CREDENTIALS_PASSWORD with your values from
the “Clever Cloud addon dashboard” presented previously.
Analyze Code
...
DB_CONNECTION=mysql
DB_HOST=example-mysql.services.clever-cloud.com
DB_PORT=3306
DB_DATABASE=bhexamplejlml1ycqdln
DB_USERNAME=uqfksexampleme3dv
DB_PASSWORD=z5jCE59QexamplemXv74DF...
To run the migrations, go to the cloud project directory, and in the Terminal, execute the following
command (see Fig. 29-4).
Execute in Terminal
php artisan migrate
167
Practical Laravel
Besides, we must recreate the “symbolic link” from public/storage to storage/app/public. To do this,
first, manually remove the public/storage folder, go to the cloud project directory, and in the
Terminal, execute the following command.
Execute in Terminal
php artisan storage:link
Now, if you go to the Clever Cloud “phpMyAdmin” tab, you will see the tables properly created (see
Fig. 29-5).
We currently have empty tables. Let’s copy the data from our local database to our cloud database.
Open “phpMyAdmin” from your local computer. Go to the online_store database, go to the Export
tab, and select “export method” as Custom. In tables, deselect all the Structure options, and in Data,
168
Practical Laravel
select the next ones: (items, orders, password_resets, personal_access_tokens, products, and users).
Finally, click Go (see Fig. 29-6).
You will get a SQL file. This file contains our application data. Then, go to the cloud database, go to
“phpMyAdmin”, go to your cloud database, click the Import tab, select the SQL file, deselect the
Enable foreign key checks option, and click Go (see Fig. 29-7).
169
Practical Laravel
Congratulations, we have our database and data in the cloud. You can check that your data was
successfully uploaded by opening the products or users table (see Fig. 29-8).
170
Practical Laravel
Let’s create a Heroku account. First, access https://signup.heroku.com/, complete the information,
and click “Create Free Account”. Next, confirm your email address and set up your password.
Installing Git
Go to the cloud project directory, and in the Terminal, execute the following commands.
Execute in Terminal
git init
git add .
git commit -m "new laravel project"
The commands initialize a Git repository and commit the current state.
The Heroku Command Line Interface (CLI) makes it easy to create and manage our Heroku apps
directly from the Terminal. Follow the installation instructions from this link:
https://devcenter.heroku.com/articles/heroku-cli.
Deploying to Heroku
To deploy our application to Heroku, you must create a Procfile file, which tells Heroku what setting
to use to launch the web server. In the cloud project root folder, create a new file Procfile (without
any extension), and fill it with the following code.
Heroku will launch an Apache web server together with PHP to serve applications from the cloud
project’s root directory. In this case, we specify that our root directory is public/.
171
Practical Laravel
Secure assets
Heroku requires that all assets be loaded from HTTPS. So, we must modify two code lines over our
layouts.
Committing changes
Since we added a new file and modified the other two, we need to include them in a new commit.
So, go to the cloud project directory, and in the Terminal, execute the following commands.
Execute in Terminal
git add .
git commit -m "adding Procfile and secure assets"
To create a new Heroku application, we use the heroku create command. Go to the cloud project
directory, and in the Terminal, execute the following command.
172
Practical Laravel
Execute in Terminal
heroku create
This command creates a Heroku application. Sometimes, the command will open a browser tab to
prompt us to log in to the Heroku website (if that is your case, please complete the log in process).
In the end, it provides a link with our new Heroku application deployed in the cloud (see Fig. 30-1).
If you access the link (in our case, it was https://powerful-savannah-94864.herokuapp.com/), you
will see a default application with a welcome message (see Fig. 30-2).
The application’s encryption key is used by Laravel to encrypt user sessions and other information.
Its value will be read from the APP_KEY environment variable.
Go to the cloud project directory, and in the Terminal, execute the following command.
Execute in Terminal
php artisan key:generate --show
The previous command will print a key that you need to copy and paste at the end of the following
command (execute it in the Terminal). Replace the ARTISAN_KEY with the value you got from the
previous command.
Analyze Code
heroku config:set APP_KEY=base64:pN+ekIge1udSJXQR6J+al8NKewCXqG85H93RMrTPn78=
173
Practical Laravel
Go to the cloud project directory, and in the Terminal, execute the following commands. Replace
the DATABASE_CREDENTIALS_HOST, DATABASE_CREDENTIALS_DATABASE_NAME,
DATABASE_CREDENTIALS_USER, and DATABASE_CREDENTIALS_PASSWORD with your own values
(those collected from the “Clever Cloud addon dashboard”).
Pushing to Heroku
Next, it’s time to deploy our application to Heroku. Go to the cloud project directory, and in the
Terminal, execute the following command.
Execute in Terminal
git push heroku master
Execute in Terminal
heroku open
You will see the complete Online Store application running over the cloud (see Fig. 30-3).
Congratulations!
174
Practical Laravel
Note 1: If you get a 500 error, it means you didn’t configure your APP_KEY properly or some of your
database credentials.
Note 2: Heroku uses an Ephemeral filesystem. It means that any files we store on the local disk get
deleted at least once every 24 hours without doing anything. They’re also deleted every time we
push to Heroku. So, we won’t configure our product images (they won’t load). There is a solution
that requires to use Amazon S3 or another cloud image storage system. However, it is out of the
scope of this book. If you have a proper alternative, post it in the discussion zone of the book
repository.
175
Practical Laravel
There are missing elements we didn’t address in this book. So, we will show you additional
interesting links and concepts of Laravel that you can learn independently. We will also give you
some book recommendations.
Recommended books
Do you want to learn other frameworks and other types of software architectures? We have some
recommendations.
• Practical Nest.js: Develop clean MVC web applications (By Daniel Correa and Greg Lim)
https://www.amazon.com/dp/B09RKLFXD4 - This is a book where we design an MVC
architecture with Nest.js (Node.js). It is a book like this one, we design the same Online Store
application with a trending technology called Nest.js.
• Beginning Django 4: Build Full Stack Python Web Applications (By Greg Lim and Daniel
Correa) https://www.amazon.com/gp/product/B09M2N778S - This is a book where we
176
Practical Laravel
design an MVT architecture with Django (Python). We don’t have some clean code strategies
and diagrams as we did in this book, but it is a great book for beginners.
• Beginning Vue Stack: Build and Deploy a Full Stack MongoDB, Express, Vue.js, Node.js App
(By Greg Lim and Daniel Correa) https://www.amazon.com/gp/product/B09G9V44MN - Do
you want to split your application into two parts (frontend and backend)? You can use this
book as a base. We design two applications: a frontend with Vue.js (which follows an MVVM
architecture) and a backend with Express (Node.js). In the process, you will learn another
database system (MongoDB).
• Beginning Django API with React: Build Django 4 Web APIs with React Full Stack Applications
(By Greg Lim and Daniel Correa) https://www.amazon.com/dp/B09S5XZ6RK - We design two
applications: a frontend with React and a backend with Django (Python).
Across our books, we have developed an extensive range of web applications with different
architectures (not only MVC), e.g., applications with separated frontend and backend, service-
oriented architectures (SOA), and even micro-services. We are developing books like this on Express
(Node.js), Django (Python), and Spring (Java), so keep checking Daniel’s Twitter account
(@danielgarax).
We would love to get your feedback. Contact us at practicalbooksco@gmail.com. Please let us know
what you liked and what you didn’t. That’s the way all of us improve as web developers. We
constantly write to book authors with questions, suggestions, critics. Both author and readers learn
a lot in this process.
Finally, please try to leave an honest review on Amazon. This is vital so that other potential readers
can see and use your unbiased opinion to buy the book and understand what people like and didn’t
like about the book. It will only take a few minutes of your time but is very valuable to us.
“Hecho en Medellín”
177