Option 1:
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e0012edf3e80b6978849f5eff0d4b4e4c79ff1609dd1e613307e16318854d24ae64f26d17af3ef0bf7cfb710ca74755a') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
Option 2: Download Composer
Create new Project
laravel new NameOfProject
It will take few minutes to execute
To get php version using CMD
~$ php artisan --version
To run development server:-
~$ php artisan serve
Views
-
these are the pages, sees user
-
directory: project/resources/views
Controllers
- Directory: project/app/Http/controllers
- manipulates models
- example:
Route::get('/user', 'UserController@index');
- /user --> is a path after the server's home directory of project
- UserController --> controller name
- index --> function in that controller
- example:
Models
- updates views
- Database Setting: -project/vendor/.env (for xampp)
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=seven DB_USERNAME=root DB_PASSWORD=
migration
- Directory of tables: project/app/database/migrations/
- Directory of Models: project/app/User.php
- Table name should be in plural form (example: users)
- Model name should be in singular form (example: User)
To migrate database table
~$ php artisan migrate
Using this command all the tables in the migrations directory and in the database will be updated An extra table will be created in the database named migrations. This table takes care of other tables. For example if we migrate again using migrate command then the migration will not take effect if no updates avaible. How laravel know that tables are already been migrated? Because of that EXTRA TABLE called "MIGRATIONS" in database. how do they interact? Model will interact with tables for example: "User" model will interact with "users" table in DB, "Student" model will interact with "students" table in DB
Create a controller for User Model
~$ php artisan make:controller UserController
Use this header in controller file to interact with Database
use Illuminate\Support\Facades\DB;
below queries will be written inside the function of the Controller.
Insert
class UserController extends Controller
{
public function index()
{
DB::insert('insert into users (name, email, password) values (?, ?, ?)',
['Imrul', '[email protected]', 'imrul']);
$users = DB::select('select * from users');
return $users;
}
}
Select
class UserController extends Controller
{
public function index()
{
$users = DB::select('select * from users');
return $users;
}
}
Update
class UserController extends Controller
{
public function index()
{
DB::update('update users set name = ? where id = ?',
['Imrul',13]);
$users = DB::select('select * from users');
return $users;
}
}
Delete
class UserController extends Controller
{
public function index()
{
DB::delete('delete from users');
$users = DB::select('select * from users');
return $users;
}
}
Insert
class UserController extends Controller
{
public function index()
{
$user = new User();
// var_dump($user); //var dumb
// dd($user); //die and dumb, so much clean
$user->name = 'Imrul Hasan';
$user->email = '[email protected]';
$user->password = bcrypt('imrul');
$user->save();
}
}
Fetch all the data from database table (select)
class UserController extends Controller
{
public function index()
{
$user = User::all();
return $user;
}
}
all()
is a static method and not static method. Magical statement? We can see all the fields value exceptpassword
. Because password isprotected_hidden
field defined inUser.php
so that field will not come inall()
User.php
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
If we want to view the password too in window then we need to delete
password
column from that$protected
variable.
Update
class UserController extends Controller
{
public function index()
{
User::where('id',16)->update(['name' => 'Imrul Hasan']);
$user = User::all();
return $user;
}
}
Delete
class UserController extends Controller
{
public function index()
{
User::where('id',14)->delete();
}
}
On the above query
id='14'
is explicitely defined which is not a process that we will working with. User model can only interact with users table.
We can simplify the ORM query
Reduced the CRUD (ORM) - using create method
UserController.php
public function index()
{
$data = [
'name' => 'Elon',
'email' => '[email protected]',
'password' => bcrypt('password'),
'mobile' => 112298989,
];
User::create($data);
$user = User::all();
return $user;
}
mobile
column will not affect the database nor give any error becausefillable
variable inUser.php
will define which colums to be checked or inserted. Database has no column namedmobile
.
User.php
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
}
So we can see there is no column named
mobile
in$fillable
variable. So database has no worry with other columns except the columns defined in$fillable
variable.
Reduced the CRUD (ORM): Accessor
and Mutaotors
Mutator function is in
User.php
Model
Accessor function is in
User.php
Model
UserController.php
public function index()
{
$data = [
'name' => 'elon',
'email' => '[email protected]',
'password' => 'password',
];
User::create($data);
$user = User::all();
return $user;
}
password
is encrypter from User Defined function inUser.php
User.php
class User extends Authenticatable
{
use Notifiable;
//changing behavior -- Mutator --- inserting
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
//modifyting -- Accessor -- don't modify in DB --fetching
public function getNameAttribute($name)
{
return 'My name is: '.ucfirst($name);
}
}
setPasswordAttribute
name is important.set*Attributes
. Here*
will be replaced by the column name. And the column name here should be inTitle case
. Say for exmaple:Password
.
getNameAttribute
name is important.get*Attributes
. Here*
will be replaced by the column name. And the column name here should be inTitle case
. Say for exmaple:Name
.
No longer need to use the functions to bcrypt
as we will import package laravel ui
for these
In Insert Operation using ORM We don't need to define out own function for password encryption or any authentication like
bcrypt('imrul');
We will make this through below package istallation.
install first package laravel UI
make sure you close all the running file from text editor becauase It may restrict the changes to the files after importing dependencies.
- documentation --> search --> authentication introduction
~$ composer require laravel/ui
composer.json file will updated
Option 1
~$ php artisan ui vue --auth
Option 2
~$ php artisan ui:auth [suggested]
auth
,layout
directory added insideview
sub-folderhome.blade.php
will be overwriteauth
directory is addedcontroller
sub-folderhomecontroller.php
is added- Added a table in databse
migration
sub-folder
now the home page of website changes. It includes
log in
andreg
option but the UI is ugly. To fix the UI follow link below.
Install the frontend scaffolding using the ui
Artisan command
Option 1
~$ php artisan ui bootstrap [suggested]
Option 2
~$ php artisan ui vue
~$ php artisan ui react
Install Node package manager (NPM):
~$ npm install
then some dependency may need to fix
~$ npm audit fix [if required]
~$ npm run dev
The npm run dev command will process the instructions in your webpack.mix.js file. Typically, your compiled CSS will be placed in the public/css directory
all the routes are in routes/web.php
To show the all the route list using cmd
~$ php artisan route:list
home page is in
home.blade.php
which extend theapp.blade.php
@extends('layouts.app')
Now this makes contents of
app.blade.php
+home.blade.php
in one page (not one page actually, it acts like so for simplicity). Sohome.blade.php
andapp.blade.php
pages are linked together. We can now call any of the contents directly from another page.
Here
layout
is directory andapp
is file name which is shoutcut name ofapp.blade.php
. This code should be imported on top of file.
Syntax:
@extends('folder1.sub-folder2.sub-folder3.app')
We can search deeper path using this kind of syntax. That's why blade is is so powerful.
In
layouts/app.blade.php
there are navigation segment and the main segment. Inside<main> </main>
all the contents are getting using below code:
@yield('content')
On above line
@yield('content')
defines there is a codeSection
defined in any of the two pages which we linked before. And we can see there's aSecion
namedcontent
inhome.blade.php
. Code section fromhome.blade.php
given below:
@extends('layouts.app')
@section('content')
<div class="container">
<!-- codes... -->
</div>
@endsection
To change name
content
of@yield('content')
from<main> </main>
inapp.blade.php
we must change namecontent
from others files too in below directories:
views/auth/
viwes/layouts/
because if we don't, some segment will not display.
-
laravel projects, package and dependency have configuarations (config)
-
Directory:
- project/config/
- project/.env
- Configurations in productions and development are different.
For example: in .env
file below:
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
So there must be a file in
config
to get information from.env
file for RADIS settings (setting above). To find the file where its informations are included just search a file related to database inproject/config/
. Because REDIT is a database file.
One important thing to note that whenever there is a change in
.env
file. we must restart the dev server. Because the server keeps the settings in cache. So we need to restart te server to start new cache. Otherwise updated settings will not take any effect.
The below code is made with a closure function: routes/web.php
Route::get('/', function (){
// return env('APP_NAME');
// return View::make('welcome'); //using Aaliyas
return view('welcome'); //using helper function
});
-
Other config
- auth.php, etc
Data flow from .env to services to web.php via config helper function: web.php
Route::get('/', function () {
return config('services.ses.key');
});
To learn more about Laravel configuaration Configuration
return config('services.ses.key');
data flow from .env to services to web.php via config helper function
- Uploaded image will be stored in
storage/app/public
directory
From for upload an image is in home.blade.php
<div class="card-body">
<form action="/upload" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="image"/>
<input type="submit" name="upload"/>
</form>
</div>
- In the from
action ="/upload"
means it will execute in/upload
directory. So we need to create thisroute
inweb.php
- csrf = cross site request forgery.
@csrf
is important toimport
insideform
. Because without it form will not successfully executed.
- No one can submit the from with fake data Laravel automatically generates a CSRF "token" for each active user session managed by the application. This token is used to verify that the authenticated user is the one actually making the requests to the application.
When we submit
form
then an exra hiddeninput
is generated which is forcsrf token
.
The input is look like below:
<input type="hidden" name="_token" value="BlV0DglRFa5wzyCUN88H0OBSocUdz6KKts0hhDjQ">
We can find the above input using
return dd($user);
To learn more about csrf token csrf
below code is using closure: web.php
Route::post('/upload', function(){
dd(request()->all()); //the requested form will be shown
});
but above code is not secure instead use below code for injection
Route::post('/upload', function(Request $request){
// dd($request->all()); //the requested form will be shown -- same as above Route
//dd($request->file('image')); //it will shows null in page Why?
//enctype="multipart/form-data" should be used in form
//dd($request->image); //same as above line
dd($request->hasFile('image')); //check if form contains any file or not. (true/false)
});
- We have
user table
withoutavatar column
.
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
- Then, we added
avatar column
indatabase/migrations/user table
in schema
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('avatar')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
- Now we have
extra one column
in user table inmigrations
. but user table already indatabase
doesn't containthe column
. Sophp artisan migrate
command will not work for it.
Here are some command for php artisan migrate
- migrate:fresh Drop all tables and re-run all migrations
- migrate:install Create the migration repository
- migrate:refresh Reset and re-run all migrations
- migrate:reset Rollback all database migrations
- migrate:rollback Rollback the last database migration
- migrate:status Show the status of each migration
~$ php artisan migrate:refresh
Using above command the
database
willrefresh
all the tablea. So ifnew columna
were added inmigration
then thesecolumns
will also be added in thedatabase
.
To upload image we will use
UserController.php
. After uploading it doesn't get theoriginal name only
instead it contains all the details of images. So will we go todepth of laravel
to grab theimage name
only
Directory: vendor/laravel/framework/src/illuminate/Http/UploadedFile.php
public static function createFromBase(SymfonyUploadedFile $file, $test = false)
{
return $file instanceof static ? $file : new static(
$file->getPathname(),
$file->getClientOriginalName(),
$file->getClientMimeType(),
$file->getError(),
$test
);
}
Search for
original name
in that file. And found$file->getClientOriginalName()
, in a function like above. CopygetClientOriginalName
and use it inUserController.php
dd($request->image->getClientOriginalName());
UserController.php
public function uploadAvatar(Request $request)
{
if($request->hasFile('image'))
{
$filename = $request->image->getClientOriginalName();
$request->image->storeAs('images', $filename,'public');
User::find(1)->update(['avatar' => $filename]);
}
return redirect()->back();
}
To view avatar of user in Navigation bar add an
<img/>
tag in theapp.blade.php
on approprite position app.blade.php
@if(Auth::user()->avatar)
<img src="{{ Auth::user()->avator }}" alt="avatar"/>
@endif
but unfortunately the image of user didn't show here.
Fixing problem 1: dynamically update database
find logged user and update avatar:
UserController.php
public function uploadAvatar(Request $request)
{
if($request->hasFile('image'))
{
$filename = $request->image->getClientOriginalName();
$request->image->storeAs('images', $filename,'public');
auth()->user()->update(['avatar' => $filename]); //for logged user
}
return redirect()->back();
}
Fixing problem 2: View Avatar in Profile Dashboard from Storage Directory
project/storate
directory is not accesable directly.- We can access
project/public
directory directly - So We need to make a link between
project/storage/public
andproject/public
directory.
To make a link between two we need following command
~$ php artisan storage:link
After that command we can see a link of
storage
directry in theproject/public
directory. Now we can access thepublic
directory insidestorage
directory viaproject/public
directory.
NOTE: we can access only public
dir of storage
dir, Not other
dir.
Now we need to correcly path the location to get the images: app.blade.php
<img src="{{ asset('/storage/images/'.Auth::user()->avatar) }}" alt="avatar" width="60px" height="60px"/>
Problem 3: After updating the new avatar, old image still in storage
- This will take space without needing
to delete the previous avatar of user before updating the avatar
User.php
(Model)
public static function uploadAvatar($image)
{
$filename = $image->getClientOriginalName();
(new self())->deleteOldImage(); //we can not use $this in static method. so use (new self()) instead of $this
$image->storeAs('images', $filename,'public');
auth()->user()->update(['avatar' => $filename]); //for log user
}
protected function deleteOldImage()
{
if(auth()->user()->avatar)
{
Storage::delete('/public/images/' . auth()->user()->avatar);
}
}
UserController.php
public function uploadAvatar(Request $request)
{
if($request->hasFile('image'))
{
User::uploadAvatar(\$request->image);
return redirect()->back(); //success msg
}
return redirect()->back(); //err msg
}
UserController.php
session()->put('message','Avatar has been changed');
home.blade.php
@if(session()->has('message'))
<div class="alert alert-success">{{ session()->get('message')}} </div>
@endif
But the problem is message stays even after reloading. So how do we remove message after reload?
Solution: Use below code segment
UserController.php
public function uploadAvatar(Request $request)
{
if($request->hasFile('image'))
{
User::uploadAvatar($request->image);
$request -> session()->flash('message','Image Uploaded!');
return redirect()->back(); //success msg
}
$request -> session()->flash('error','Image Not Uploaded!');
return redirect()->back(); //err msg
}
home.blade.php
@if(session()->has('message'))
<div class="alert alert-success">{{ session()->get('message')}} </div>
{{ session()->forget('message') }}
@elseif(session()->has('error'))
<div class="alert alert-danger">{{ session()->get('error')}} </div>
@endif
But we can do that kind of messages more easier way for that when we redirect to the back page.
FINALLY
UserController.php
public function uploadAvatar(Request $request)
{
if($request->hasFile('image'))
{
User::uploadAvatar(\$request->image);
return redirect()->back()->with('message','Image Uploaded!');
}
return redirect()->back()->with('error','Image Not Uploaded!');
}
home.blade.php
(no change)
@if(session()->has('message'))
<div class="alert alert-success">{{ session()->get('message')}} </div>
{{ session()->forget('message') }}
@elseif(session()->has('error'))
<div class="alert alert-danger">{{ session()->get('error')}} </div>
@endif
We can make another file for
flash message
so that we dont need to explicitely write the code segment again for every file whenever needed. So now we just call the file when needed this kind of flash message.
views/layouts/flash.blade.php
@if(session()->has('message'))
<div class="alert alert-success">{{ session()->get('message')}} </div>
{{ session()->forget('message') }}
@elseif(session()->has('error'))
<div class="alert alert-danger">{{ session()->get('error')}} </div>
@endif
views/home.blade.php
@include('layouts.flash')
we can create
componets
by installing a library which is laravel's greate feature.
To learn more about components
To create a class based component follow the command
~$ php artisan make:component Alert
- The command will create two directory
app/view/componets
resources/views/components/alert.blade.php
Copy the code that was in flash.blade.php
and Past it to alert.blade.php
alert.blade.php
<div>
@if(session()->has('message'))
<div class="alert alert-success">{{ session()->get('message')}} </div>
{{ session()->forget('message') }}
@elseif(session()->has('error'))
<div class="alert alert-danger">{{ session()->get('error')}} </div>
@endif
</div>
home.blade.php
<x-alert/>
instead of using
@include('layouts.flash')
now we are using use<x-alert/>
Advantages of
components
like<x-alert>
over@include
: We can not pass any data inside included file. but using component we can do that
how to create table?
Ans: using migration, need model to interact with table
Table name: todos
- title: string
- complited: boolean
Model Name: todo
(which will interact with table todos)
So we need 4 things to create a table
- model:
todo
- migration:
todos
[table] - routes: Manual Creation
- controller:
TodoController
How we create all these things?
~$ php artisan make:model
~$ php artisan make:migration
~$ php artisan make:controller
But laravel is so smart to do these thing in easy. So we can do all the three command in one line like below:
~$ php artisan make:model Todo -mc [Suggested]
flags:
m
= migrationc
= controller- Model, Migration and Controller is created.
Results
Table:
todos
. Directory:database/migrations/
Model:
todo
. Directory:view/
Controller:
TodoController
. Directory:Http/Controller/
Now we will create structure of todo table in migrations
migratioon/todos table
class CreateTodosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
Then we need to add
table
to database
~$ php artisan migrate
Dont use
fresh
orrefresh
migration command, if so then we will loss our datas from table.
We have done 3 task out of 4 now we need to make routes.
Create a
todos
directory inside theresources/views/
So that all the todo related files will be stored in that directory.
Creating a route in web.php
for index
web.php
Route::get('/todos', 'TodoController@index');
Inside
todos
directiory file name isindex.blade.php
.index
function inside theTodoController
Create a file named
index.blade.php
in the foldertodos
Creating a route in web.php
for create
todo
web.php
Route::get('/todos/create', 'TodoController@create');
Inside
todos
directiory file name iscreate.blade.php
.create
function inside theTodoController
Create a file named
create.blade.php
in the foldertodos
Creating a route in web.php
for edit
todo
web.php
Route::get('/todos/edit', 'TodoController@edit');
Inside
todos
directiory file name isedit.blade.php
.edit
function inside theTodoController
Create a file named
edit.blade.php
in the foldertodos
TodoController.php
class TodoController extends Controller
{
public function index()
{
return view('todos.index'); //inside todos directiory file named index.blade.php
}
public function create()
{
return view('todos.create'); //inside todos directiory file named index.blade.php
}
public function edit()
{
return view('todos.edit'); //inside todos directiory file named index.blade.php
}
}
Now create a form inside the create.blade.php
<div class="text-center pt-10">
<h1 class="text-2xl">What next you need To-Do</h1>
<form action="/todos/create" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" class="py-2 px-2 border"/>
<input type="submit" value="Create" class="p-2 border rounded"/>
</form>
</div>
Here form
method="POST"
so we need to create a function for storing data insideTodoController
viaRoute::post
fromweb.php
In this
form
We useTailwind
framework forCSS
instead ofbootstrap
.
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
web.php
Route::post('/todos/create','TodoController@store'); //in web.php
Add the store function inside the TodoController.php
TodoController.php
public function store(Request $request)
{
//dd($request->all());
Todo::create(\$request->all());
}
Now after submitting the form error:
Add [_token] to fillable property to allow mass assignment on [App\Todo].
Add below line to Todo.php
(Model)
class Todo extends Model
{
protected $fillable = ['_token']; //added line
}
After submitting the form another error:
Column not found: 1054 Unknown column '\_token' in 'field list' (SQL: insert into todos ( \_token
,updated_at
,created_at) values
Edit below line to Todo.php
(Model)
class Todo extends Model
{
protected $fillable = ['title']; //added line
}
Again error:
completed column is required
Edit below line of code from
todo
table inmigrations/
$table->boolean('completed')->default(false);
Now the table look like this:
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
It makes
dafault value
of completed asfalse
, no need to pass the data,default value = 0
now
And then
~$ php artisan migrate:fresh
Never use in production, because it erash all the datas in table
~$ php artisan serve
refresh the page and now no error and data is saved in the database
TodoController.php
public function store(Request $request)
{
//dd($request->all());
Todo::create(\$request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
But the
message
is not showing weather uploaded or not.
Solution: add <x-alert/>
in the from segment of create.blade.php
.
After adding the
<x-alert/>
the message appears but without anyCSS
. Because we usetailwind
notbootstrap
Solution: We need to change the alert.blade.php
file
alart.blade.php
<div>
@if(session()->has('message'))
<div class="py-4 px-2 bg-green-400">{{ session()->get('message')}} </div>
{{ session()->forget('message') }}
@elseif(session()->has('error'))
<div class="py-4 px-2 bg-red-300">{{ session()->get('error')}} </div>
@endif
</div>
Now it works.......
But another problem arises when we Click Create button without input anything in the text filled. We need to handle this like before we did.
Solves:
public function store(Request $request)
{
if(!$request->title)
{
return redirect()->back()->with('error', 'Please Give Title');
}
Todo::create(\$request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
but above code is not a good way of validation message. because many more field may have in database. So its bad way to do
condition
for each and everyfields
like that.
Solution:
Displaying The Validation Errors
TodoController.php
public function store(Request $request)
{
$request->validate([
'title'=>'required'
]);
Todo::create($request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
But the problem is no error message apppears when submitting without message. Because we do this kind of validation the error will go inside the session as a name of
errors
noterror
. Previously that waserror
. That means when we use this kind of validation the validation autumatically returns and shows the error message aserrors
Solution: Put a segment of code from Documentation to alert.blade.php
after the first if-else-endif
condition
alert.blade.php
<div>
@if(session()->has('message'))
<div class="py-4 px-2 bg-green-400">{{ session()->get('message')}} </div>
{{ session()->forget('message') }}
@elseif(session()->has('error'))
<div class="py-4 px-2 bg-red-300">{{ session()->get('error')}} </div>
@endif
@if ($errors->any())
<div class="py-2 px-2 bg-red-300">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
Another problem also stays. When we pass a title name more than 255 of length then another error occurs. We need to handle this problem.
max:value --> Laravel Documentation
Solution:
$request->validate([
'title'=>'required|max:255'
]);
Now the error message:
The title may not be greater than 255 characters.
But I want to display my own message. How to do that?
Solution:
TodoController.php
public function store(Request $request)
{
$rules =[
'title'=>'required|max:255',
];
$messages = [
'title.max' => 'ToDo title should not be greater than 255 chars',
];
$validator = Validator::make($request->all(), $rules, $messages);
//this code will not prevent below code code from eecuting so error will appears.
//so we need below segment to check
if ($validator->fails())
{
return redirect()->back()
->withErrors($validator)
->withInput();
}
Todo::create($request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
In laravel there is very easy for validating a form. So we don't need to define validation rules and function of our own. We can just use the built in system by laravel.
Documentation: From Validation
For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that contain validation logic. To create a form request class, use the make:request Artisan CLI command:
To create our own form request we have command in laravel
Create a Request
~$ php artisan make:request TodoCreateRequest
- Directory:
App\Http\Requests\
The new file looks like below:
TodoCreateRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class TodoCreateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
If the authorize method
returns false
, a HTTP response with a 403 status code will automatically be returned and your controller method will not execute. If you plan to have authorization logic in another part of your application,return true
from the authorize method:
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
Now
$rules
move fromTodoController.php
toTodoCreateRequest.php
Now TodoCreateRequest.php
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title'=>'required|max:255',
];
}
Now how we can use these rules? We will use our own form request
TodoCreateRequest
instead of default parentRequest
. It will not affect any problem becauseTodoCreateRequest
indirectly extendsRequest
.
Now we deleted the validation method that was written in
TodoController.php
becuase now ourrequests
are comming from theTodoCreateRequests
Use below header in
TodoController.php
use App\Http\Requests\TodoCreateRequest;
TodoCreateController.php
public function store(TodoCreateRequest $request)
{
Todo::create($request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
Now check in the dev server.
Now we are facing the problem of custom validation rules again. Because we want to display our
own message
.
Solution: Customizing The Error Messages
Add the below function in ToCreateRequest.php
public function messages()
{
return [
'title.max' => 'ToDo title should not be greater than 255 chars',
];
}
Now the ToCreateRequest.php
looks like below
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class TodoCreateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title'=>'required|max:255',
];
}
public function messages()
{
return [
'title.max' => 'ToDo title should not be greater than 255 chars',
];
}
}
Now the
store
function ofTodoController
look very clean. And the function now do 2 things. 1. Create and 2. Redirect. And all the business logic of validation task are now moved in to its own dedicated file that isTodoCreateRequest
.
How to get all the todos from database [todos table]
Flow:
web.php
--->Route::get('/todos', 'TodoController@index');
TodoController.php
--->index()
TodoController.php
public function index()
{
$todos = Todo::all();
return $todos;
// return view('todos.index');
}
How can we pass the $todos
values from this page to todos/index.blade.php
file?
Solution
TodoController.php
public function index()
{
$todos = Todo::all();
// return $todos;
return view('todos.index')->with(['todos'=> $todos]);
}
todos/index.blade.php
<h3>All todos</h3>
<ul>
@foreach($todos as $todo)
<li>
{{ $todo->title }}
</li>
@endforeach
</ul>
But the look is not good. Now we will make it look better with the help of
tailwind
css.
After adding CSS with HTML structure.
todos/index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
<title>Todos</title>
</head>
<body>
<div class="text-center pt-10">
<h1 class="text-2xl">All your ToDos</h1>
<ul>
@foreach($todos as $todo)
<li>
{{ $todo->title }}
</li>
@endforeach
</ul>
</div>
</body>
</html>
Its its not good practice to have some same pice of code segment in multiple pages. Rather we can save common segemnt in another page. So that we can call it when needed.
Create a file named
layout.blade.php
insideviews/todos
directory. And this layout is only related to our todo. This file contains some common elements of othertodo
pages.
After Simplifying, the files look like below:
views/todos/layout.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
<title>Todos</title>
</head>
<body>
<div class="text-center pt-10">
@yield('content')
</div>
</body>
</html>
todos/index.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl">All your ToDo's</h1>
<ul>
@foreach($todos as $todo)
<li>
{{ $todo->title }}
</li>
@endforeach
</ul>
@endsection
todos/create.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl">What next you need To-Do</h1>
<x-alert/>
<form action="/todos/create" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" class="py-2 px-2 border"/>
<input type="submit" value="Create" class="p-2 border rounded"/>
</form>
@endsection
TodoController.php
public function index()
{
$todos = Todo::all();
return view('todos.index', compact('todos'));
}
We can also simplify
index()
function ofTodoController.php
. instead ofwith()
we can usecompact()
method of PHP. Insidecompact('variable')
.variable
should match with the Variable name. And no need to provide$
sign insidecompact()
method.
-
We need a
create
button in thetodos/index.blade.php
to create new todo. -
We need a
back
button in thetodos/create.php
to come back toindex
page. -
We need
edit
buttons for every data in thetodos/index.blade.php
to edit value.
todos/index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-center py-2">
<p>{{$todo->title}}</p>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="mx-5 py-1 px-1 bg-orange-400 cursor-pointer rounded text-white">Edit</a>
</li>
@endforeach
</ul>
@endsection
here in the
index.blade.php
when we edit a row we need to pass theid
oftitle
inurl
so that we can know whichtitle
should be edited through unique columnid
. So we passed theid
into theurl
.
todos/create.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl">What next you need To-Do</h1>
<x-alert/>
<form action="/todos/create" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" class="py-2 px-2 border"/>
<input type="submit" value="Create" class="p-2 border rounded"/>
</form>
<a href="/todos" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
But now the problem is
404 | Not Found
when edit. Lets modify theroute
.
web.php
Route::get('/todos/id/edit', 'TodoController@edit');
Still not working. Because this
id
is dynamic. So have a look for solution in documentation Required Parameter. Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters:
web.php
Route::get('/todos/{id}/edit', 'TodoController@edit');
Now it works.
Now its time to work with TodoController@edit
TodoController.php
public function edit($id)
{
// dd($id);
$todo = Todo::find($id);
// return $todo;
return view('todos.edit', compact('todo'));
//inside todos directiory file named edit.blade.php
}
todos/edit.blade.php
@extends('todos.layout')
@section('content')
{{ $todo->title }}
@endsection
When injecting a model ID to a route or controller action, for example:
find($id)
, you will often query to retrieve the model that corresponds to that ID. Laravel route model binding provides a convenient way to automatically inject the model instances directly into your routes. For example, instead of injecting a user's ID, you can inject the entire User model instance that matches the given ID.
Documentation: Route Model Binding
web.php
Route::get('/todos/{todo}/edit', 'TodoController@edit');
TodoController.php
public function edit(Todo $todo)
{
dd($todo->title);
return view('todos.edit', compact('todo'));
}
Instead of using
$id
as parameter ofedit()
function. We Pass theModel
name following by thevariable
name. And thevariable
name should match thedynamic
variable name passes throughRoute
. The$todo
variable contains whole information related to that id.
Here
{todo}
inRoute
and(Model $todo)
. Note: in route dynamic variable, there is no$
sign starting of the variable.
- What if we pass the
title
instead ofid
?- this will not work because
{ todo }
alaways try to find throughid
- this will not work because
Documentation: Customizing The Key
below Route will now find through title
.
web.php
Route::get('/todos/{todo:title}/edit', 'TodoController@edit');
todos/index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-center py-2">
<p>{{$todo->title}}</p>
<a href="{{'/todos/'.$todo->title.'/edit'}}" class="mx-5 py-1 px-1 bg-orange-400 cursor-pointer rounded text-white">Edit</a>
</li>
@endforeach
</ul>
@endsection
So this functionality is very
unique
things. That's why laravel is so powerful.
If you always want to find the todo with
title
on route model binding.
Documentation: Customizing The Default Key Name
Add below function to Todo.php
public function getRouteKeyName()
{
return 'title';
}
Todo.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Todo extends Model
{
protected $fillable = ['title'];
public function getRouteKeyName()
{
return 'title';
}
}
web.php
Route::get('/todos/{todo}/edit', 'TodoController@edit');
So we don't need to use
{ todo:title}
instead of{ todo }
as we customized the default key name inTodo.php
Model.
todos/index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-center py-2">
<p>{{$todo->title}}</p>
<a href="{{'/todos/'.$todo->title.'/edit'}}" class="mx-5 py-1 px-1 bg-orange-400 cursor-pointer rounded text-white">Edit</a>
</li>
@endforeach
</ul>
@endsection
Now we will update todo
title
. For that we need aform
or a simpletextbox
inedit.blade.php
.
todos/edit.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl">Update this Todo list</h1>
<x-alert/>
<form action="/todos/update" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border"/>
<input type="submit" value="Update" class="p-2 border rounded"/>
</form>
<a href="/todos" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
web.php
Route::patch('/todos/{todo}/update','TodoController@update');
Now we need a
patch
request instead ofpost
request. There is alsoput
request. Both will work. And we need to update a specifictodo
so the route will be simiter tostore
route. like inside link we need to pass the todoid
-->{todo}
Difference between
GET
,POST
,PATCH
andPUT
: Difference
Creating a function named update
inside TodoController.php
.
TodoController.php
public function update(Todo $todo)
{
//update code
}
Now how can we grab the
id
in between these ->action="/todos/update"
Named routes allow the convenient generation of URLs or redirects for specific routes. You may specify a name for a route by chaining the name method onto the route definition:
Documentation: Named Route.
web.php
Route::patch('/todos/{todo}/update','TodoController@update')->name('todo.update');
what are you going to do? ->
todo
and the task is ->update
. Soname('todo.update')
. This name route will be used inside<form>
as anaction
for pathing.
And the todos/edit.blade.php
will be like below:
@extends('todos.layout')
@section('content')
<h1 class="text-2xl">Update this Todo list</h1>
<x-alert/>
<form method="post" action="{{route('todo.update',$todo->id)}}" class="py-5">
@csrf
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border"/>
<input type="submit" value="Update" class="p-2 border rounded"/>
</form>
<a href="/todos" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
Now it works.... Now if we
inspect
element inside thehttp://127.0.0.1:8000/todos/1/edit
page, we can seeaction="http://127.0.0.1:8000/todos/1/update"
for theform
.
Let's check if update function works or not.
TodoController.php
public function update(Request $request, Todo $todo)
{
dd($request->all());
}
We need
Request
inside the function parameter.
But update function didn't work. Error:
The POST method is not supported for this route. Supported methods: PATCH.
So we need a
patch
method. We need a hack. We need to provide amethod
field and saypath
is the method. After updating theedit.blade.php
file.
edit.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl">Update this Todo list</h1>
<x-alert/>
<form method="post" action="{{route('todo.update',$todo->id)}}" class="py-5">
@csrf
@method('patch')
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border"/>
<input type="submit" value="Update" class="p-2 border rounded"/>
</form>
<a href="/todos" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
Now we will make the update
operation inside the update
function on TodoController
.
TodoController.php
public function update(Request $request, Todo $todo)
{
$todo->update(['title' => $request ->title]);
return redirect()->back()->with('message','Updated!');
}
Update operation successful! And the page is redirect to the edit page.
Below is the way to redirect to the All Todo
page that's the main page.
TodoController.php
public function update(Request $request, Todo $todo)
{
$todo->update(['title' => $request ->title]);
return redirect(route('todo.index'))->with('message','Updated!');
}
we also need to provide the
Named Route
ofindex
route inweb.php
so that we canroute
it from anywhere with theNamed Route
only. We don't need to explicitely write the path there. Named routes is an important feature in the Laravel framework. It allows you to refer to the routes when generating URLs or redirects to the specific routes. In short, we can say that the naming route is the way of providing a nickname to the route.
web.php
Route::get('/todos', 'TodoController@index')->name('todo.index');
We also need to add
<x-alert/>
in theindex.blade.php
to view the message.
index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-center py-2">
<p>{{$todo->title}}</p>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="mx-5 py-1 px-1 bg-orange-400 cursor-pointer rounded text-white">Edit</a>
</li>
@endforeach
</ul>
@endsection
Solved!!!
Previously we have use validation which is
FormRequest
when we were storing thetodo
. We can create anotherFormRequest
for update. But we want same kind of functionality, exactly same validation on the update. So we can use the sameFormRequest
also.
Just replace the normal Requst
with the FormRequest
that's the TodoCreateRequest
.
public function update(TodoCreateRequest $request, Todo $todo)
{
$todo->update(['title' => $request ->title]);
return redirect(route('todo.index'))->with('message','Updated!');
}
We can now successfully complete the validation.
Suppose, you will have the condition when you need to create a different form request for
update
only. For example, for thestore()
function if you have some kind ofupload
operation. Then you needimage
valodation also. So the validation forTodoCreateRequest
will contains validation fortitle
as well asimage
. But the problem is inupdate()
operation you just want to update thetitle
only not theimage
. Then if you use the sameFormRequest
also then you must update theimage
also because thatFromRequest
containsimage
validation also. So need to create anotherFormRequest
for update operation.
Now we beautify the message in page. Because the message block whole page with horizozntal line. And fix some padding and etc.
After Cleaning up
layout.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
<title>Todos</title>
</head>
<body>
<div class="text-center flex justify-center pt-10">
<div class="w-1/3 border border-grey-400 rounded py-4">
@yield('content')
</div>
</div>
</body>
</html>
create.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl border-b pb-4">What next you need To-Do</h1>
<x-alert/>
<form action="/todos/create" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" class="py-2 px-2 border"/>
<input type="submit" value="Create" class="p-2 border rounded"/>
</form>
<a href="/todos" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
edit.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl border-b pb-4">Update this Todo list</h1>
<x-alert/>
<form method="post" action="{{route('todo.update',$todo->id)}}" class="py-5">
@csrf
@method('patch')
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border"/>
<input type="submit" value="Update" class="p-2 border rounded"/>
</form>
<a href="/todos" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-between px-2 py-2">
<p>{{$todo->title}}</p>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="mx-5 py-1 px-1 bg-orange-400 cursor-pointer rounded text-white">Edit</a>
</li>
@endforeach
</ul>
@endsection
After beautify the index
page.
TodoConroller.php
public function index()
{
$todos = Todo::orderBy('completed')->get();
return view('todos.index', compact('todos'));
}
Documentation: Order By
Use
Todo::orderBy('completed')->get();
instead ofTodo::all()
. We will useget()
instead ofall()
when we have some task likeorderBy
.
Completed task will be in green
mark on the other hand incomplete task will be in gray
mark. using below condition:
@if($todo->completed)
<span class="fas fa-check text-green-300 px-2"></span>
@else
<span class="fas fa-check text-gray-300 cursor-pointer px-2"></span>
@endif
Completed task title is visualizd by linethrough
like below code:
@if($todo->completed)
<p class="line-through">{{$todo->title}}</p>
@else
<p class="line-through">{{$todo->title}}</p>
@endif
todos/index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-between px-2 py-2">
@if($todo->completed)
<p class="line-through">{{$todo->title}}</p>
@else
<p>{{$todo->title}}</p>
@endif
<div>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="text-orange-400 cursor-pointer text-white">
<span class="fas fa-edit px-2"></span>
</a>
@if($todo->completed)
<span class="fas fa-check text-green-300 px-2"></span>
@else
<span class="fas fa-check text-gray-300 cursor-pointer px-2"></span>
@endif
</div>
</li>
@endforeach
</ul>
@endsection
We created the below submit
form.
<form style="display:none" id="{{'form-complete-'.$todo->id}}" method="post" action="{{ route('todo.complete', $todo->id) }}">
@csrf
@method('put')
</form>
We added below line of code to submit form using JS
<span onclick="event.preventDefault();
document.getElementById('form-complete-{{$todo->id}}')
.submit()"
class="fas fa-check text-gray-300 cursor-pointer px-2">
</span>
We call the
form
using theid
selector with JS.
And the index
page will look like below:
todos/index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-1 px-1 bg-blue-400 cursor-pointer rounded text-white">Create New</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-between px-2 py-2">
@if($todo->completed)
<p class="line-through">{{$todo->title}}</p>
@else
<p>{{$todo->title}}</p>
@endif
<div>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="text-orange-400 cursor-pointer text-white">
<span class="fas fa-edit px-2"></span>
</a>
@if($todo->completed)
<span class="fas fa-check text-green-300 px-2">
</span>
@else
<span onclick="event.preventDefault();
document.getElementById('form-complete-{{$todo->id}}')
.submit()"
class="fas fa-check text-gray-300 cursor-pointer px-2">
</span>
<form style="display:none" id="{{'form-complete-'.$todo->id}}" method="post" action="{{ route('todo.complete', $todo->id) }}">
@csrf
@method('put')
</form>
@endif
</div>
</li>
@endforeach
</ul>
@endsection
web.php
Route::put('/todos/{todo}/complete','TodoController@complete') -> name('todo.complete');
patch
andput
is similar things so we can also use it.
TodoController.php
public function complete(Todo $todo)
{
$todo->update(['completed'=> true]);
return redirect()->back()->with('message','Task Marked as Completed!!!');
}
The update operation.
Todo.php
class Todo extends Model
{
protected $fillable = ['title','completed'];
}
Initially the
$fillable
variable didn't contains thecompleted
column. So our value ofcompleted
column didn't take any effect in DB after updating. After we added the column in the variable. And the update process is successfull.
This segment is fully same as
complete
task. So I am not going to describe the process.
web.php
Route::delete('/todos/{todo}/incomplete','TodoController@incomplete') -> name('todo.incomplete');
delete
is another alternative ofpatch
orput
.
**TodoController.php
**
public function incomplete(Todo $todo)
{
$todo->update(['completed'=> false]);
return redirect()->back()->with('message','Task UnMarked as Completed!!!');
}
**todos/index.blade.php
**
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4 px-4">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-2 text-blue-400 cursor-pointer text-white">
<span class="fas fa-plus-circle"></span>
</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-between px-2 py-2">
@if($todo->completed)
<p class="line-through">{{$todo->title}}</p>
@else
<p>{{$todo->title}}</p>
@endif
<div>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="text-orange-400 cursor-pointer text-white">
<span class="fas fa-edit px-2"></span>
</a>
@if($todo->completed)
<span onclick="event.preventDefault();
document.getElementById('form-incomplete-{{$todo->id}}')
.submit()" class="fas fa-check text-green-300 cursor-pointer px-2">
</span>
<form style="display:none" id="{{'form-incomplete-'.$todo->id}}" method="post" action="{{ route('todo.incomplete', $todo->id) }}">
@csrf
@method('delete')
</form>
@else
<span onclick="event.preventDefault();
document.getElementById('form-complete-{{$todo->id}}')
.submit()"
class="fas fa-check text-gray-300 cursor-pointer px-2">
</span>
<form style="display:none" id="{{'form-complete-'.$todo->id}}" method="post" action="{{ route('todo.complete', $todo->id) }}">
@csrf
@method('put')
</form>
@endif
</div>
</li>
@endforeach
</ul>
@endsection
`web.php1
Route::delete('/todos/{todo}/delete','TodoController@delete') -> name('todo.delete');
todos/index.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4 px-4">
<h1 class="text-2xl">All your ToDos</h1>
<a href="/todos/create" class="mx-5 py-2 text-blue-400 cursor-pointer text-white">
<span class="fas fa-plus-circle"></span>
</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-between px-2 py-2">
@include('todos.complete-button')
@if($todo->completed)
<p class="line-through">{{$todo->title}}</p>
@else
<p>{{$todo->title}}</p>
@endif
<div>
<a href="{{'/todos/'.$todo->id.'/edit'}}" class="text-orange-400 cursor-pointer text-white">
<span class="fas fa-edit px-2"></span>
</a>
<span class="fas fa-trash text-red-500 px-2 cursor-pointer"
onclick="event.preventDefault();
if(confirm('Are you sure to delete?'))
{
document.getElementById('form-delete-{{$todo->id}}')
.submit()
}"></span>
<form style="display:none" id="{{'form-delete-'.$todo->id}}" method="post" action="{{ route('todo.delete', $todo->id) }}">
@csrf
@method('delete')
</form>
</div>
</li>
@endforeach
</ul>
@endsection
Some changes in
index
. We move the code ofcomplete
andincomplete
logic in another file for simplicity, namedcomplete-button.blade.php
.
Any user may delete the task accidentaly that's why there will display an alert window after click on delete button, if user is sure to delete the task.
todos/complete-button.blade.php
@if($todo->completed)
<span onclick="event.preventDefault();
document.getElementById('form-incomplete-{{$todo->id}}')
.submit()" class="fas fa-check text-green-300 cursor-pointer px-2">
</span>
<form style="display:none" id="{{'form-incomplete-'.$todo->id}}" method="post" action="{{ route('todo.incomplete', $todo->id) }}">
@csrf
@method('delete')
</form>
@else
<span onclick="event.preventDefault();
document.getElementById('form-complete-{{$todo->id}}')
.submit()"
class="fas fa-check text-gray-300 cursor-pointer px-2">
</span>
<form style="display:none" id="{{'form-complete-'.$todo->id}}" method="post" action="{{ route('todo.complete', $todo->id) }}">
@csrf
@method('put')
</form>
@endif
TodoController.php
public function delete(Todo $todo)
{
$todo->delete();
return redirect()->back()->with('message', $todo->title.' Task Deleted!!!');
}
In
web.php
we created 6 routes fortodo
thats are for insert, update and delete operation. But we can combine all together in a route calledRosource Route
.
Documentation Rosource ROute
First thing you requred a
resource controller
which is done by this command ->php artisan make:controller PhotoController --resource
. But because we have already done with the controllers inweb.php
, we can directly use like thisRoute::resource('photos', 'PhotoController');
Before adding the resource route, the 6 defined route in web.php
is given below:
Route::get('/todos', 'TodoController@index')->name('todo.index');
Route::get('/todos/create', 'TodoController@create');
Route::post('/todos/create','TodoController@store');
Route::get('/todos/{todo}/edit', 'TodoController@edit');
Route::patch('/todos/{todo}/update','TodoController@update') -> name('todo.update');
Route::delete('/todos/{todo}/destroy','TodoController@delete') -> name('todo.delete');
So the below only one route itself will replace the 6 defined routes.
Route::resource('/todo','TodoController');
Notice that we set
path
of the resource route as/todo
instead of/todos
.
Actions Handled By Resource Controller
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /photos | index | photos.index |
GET | /photos/create | create | photos.create |
POST | /photos | store | photos.store |
GET | /photos/{photo} | show | photos.show |
GET | /photos/{photo}/edit | edit | photos.edit |
PUT/PATCH | /photos/{photo} | update | photos.update |
DELETE | /photos/{photo} | destroy | photos.destroy |
let's create a resource route and compare wether we we read this resource route or not using following command.
~$ php artisan make:controller TodoResourceController --resource
Now I can differentiated with exixting controller and the controller I just created.
A file, named TodoResourceController
is created in app\Http\Controllers\TodoResourceController.php
directory.
like below:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TodoResourceController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
We don't need to use this function in
TodoResourceController
. Because we have implemented those function inTodoController.php
before. And those function override these functions. So don't worry about this.
To see all the route name including function name type the command
php artisan route:list
Edit the name of delete
function to destroy
public function destroy(Todo $todo)
{
$todo->delete();
return redirect()->back()->with('message', $todo->title.' Task Deleted!!!');
}
index.blade.php
(updated)
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4 px-4">
<h1 class="text-2xl">All your ToDos</h1>
<a href="{{route('todo.create')}}" class="mx-5 py-2 text-blue-400 cursor-pointer text-white">
<span class="fas fa-plus-circle"></span>
</a>
</div>
<x-alert/>
<ul class="my-5">
@foreach($todos as $todo)
<li class="flex justify-between px-2 py-2">
@include('todos.complete-button')
@if($todo->completed)
<p class="line-through">{{$todo->title}}</p>
@else
<p>{{$todo->title}}</p>
@endif
<div>
<a href="{{ route('todo.edit',$todo->id )}}" class="text-orange-400 cursor-pointer text-white">
<span class="fas fa-edit px-2"></span>
</a>
<span class="fas fa-trash text-red-500 px-2 cursor-pointer"
onclick="event.preventDefault();
if(confirm('Are you sure to delete?'))
{
document.getElementById('form-delete-{{$todo->id}}')
.submit()
}"></span>
<form style="display:none" id="{{'form-delete-'.$todo->id}}" method="post" action="{{ route('todo.destroy', $todo->id) }}">
@csrf
@method('delete')
</form>
</div>
</li>
@endforeach
</ul>
@endsection
We just change some of the route using
named routes
instead ofhard code path
.
edit.blade.php
(updated)
@extends('todos.layout')
@section('content')
<h1 class="text-2xl border-b pb-4">Update this Todo list</h1>
<x-alert/>
<form method="post" action="{{route('todo.update',$todo->id)}}" class="py-5">
@csrf
@method('patch')
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border"/>
<input type="submit" value="Update" class="p-2 border rounded"/>
</form>
<a href="{{ route ('todo.index')}}" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
We just change some of the route using
named routes
instead ofhard code path
create.blade.php
@extends('todos.layout')
@section('content')
<h1 class="text-2xl border-b pb-4">What next you need To-Do</h1>
<x-alert/>
<form action="{{ route('todo.store') }}" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" class="py-2 px-2 border"/>
<input type="submit" value="Create" class="p-2 border rounded"/>
</form>
<a href="{{route('todo.index')}}" class="m-5 py-1 px-1 bg-white-400 border cursor-pointer rounded text-black">Back</a>
@endsection
We just change some of the route using
named routes
instead ofhard code path
Documentation MiddleWare
Middle Ware Dir:
E:\github\seven\app\Http\Middleware\
If we go to
E:\github\seven\app\Http\Kernel.php
we will se we have a list of middleware that we already applied on Laravel. Laravel do all these things out of the box.
Route Model Binding that actually works with these kind of
middleware
.
But we gonna use the middleware which is called
Authenticate
middleware.
If
user
islogged in
then will will do something, otherwise we willredirect
back that means some routes areprotected
.
To assign middleware to all routes within a group, you may use the middleware method before defining the group. Middleware are executed in the order they are listed in the array:
We will use the GroupedMiddleWare
now. Route Groups
Way 1:
web.php
Route::middleware('auth')->group(function(){
Route::resource('/todo','TodoController');
Route::put('/todos/{todo}/complete','TodoController@complete') -> name('todo.complete');
Route::delete('/todos/{todo}/incomplete','TodoController@incomplete') -> name('todo.incomplete');
});
We put all the
todos
route inside thegroup-middleware
. So that no one can visits those routes without authentication.
Way 2:
TodoController.php
public function __construct()
{
$this->middleware('auth');
}
We can also set the middleware in the
Controller
that's theTodoController.php
instead of set inweb.php
.
So we created a constructor above all functions like above.
Way 3:
web.php
Route::resource('/todo','TodoController')->middleware('auth');
This type of way is use for individual
route
.
From the above 3 ways of middleware
technique I think Controller
middle ware is easier to manipulate.
If we want to access todo
index page without authenticating. Other pages (delete, update, add)
need authentication. How to do that?
TodoController.php
public function __construct()
{
$this->middleware('auth')->except('index');
}
This means we need
authentication
without index page.
One to Many relationship: Elequent Relationship
First create a One to Many
relationship among user
and todos
User.php
public function todos()
{
return $this->hasMany(Todo::class);
}
Remember, Eloquent will automatically determine the proper foreign key column on the Comment model. By convention, Eloquent will take the "snake case" name of the owning model and suffix it with
_id
. So, for this example, Eloquent will assume the foreign key on the Comment model ispost_id
.
In our case Model name is
User
, So the foreign key isuser_id
. We we needuser_id
in our Todo table.
We added below column in Todo
table for foreign key constraint.
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
Here firts we create an
user_id
column inTodo
table. Then we reference thisuser_id
to columnuser_id
onUser
table.
Now the table will look like below:
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->boolean('completed')->default(false);
$table->timestamps();
});
Documentation: Foreign Key Constraint
TodoController.php
public function store(TodoCreateRequest $request)
{
$userId = auth()->id();
$request['user_id'] = $userId;
Todo::create($request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
To grab the
user
and save theuser_id
intodo
table, we use the first 2 lines of code on above function.
Another preblem arises. We need to Mask Assignemnt on Model
Todo.php
protected $fillable = ['title','completed','user_id'];
Above code should be written in the model. Here
user_id
is added now.
Documentation: Insering and Updating Related Model
We added dd(auth()->user());
in store
function in TodoController.php
App\User {#1221 ▼
#fillable: array:4 [▶]
#hidden: array:2 [▶]
#casts: array:1 [▶]
#connection: "mysql"
#table: "users"
#primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:9 [▶]
#original: array:9 [▶]
#changes: []
#classCastCache: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
+timestamps: true
#visible: []
#guarded: array:1 [▶]
#rememberTokenName: "remember_token"
}
We can see there is no relationship
#relations: []
.
Now change with dd(auth()->user()->todos);
and we can se below:
Illuminate\Database\Eloquent\Collection {#1242 ▼
#items: array:1 [▼
0 => App\Todo {#1245 ▶}
]
}
Now change with dd(auth()->user()->todos());
and we can se below:
Illuminate\Database\Eloquent\Relations\HasMany {#1241 ▼
#foreignKey: "todos.user_id"
#localKey: "id"
#query: Illuminate\Database\Eloquent\Builder {#1214 ▶}
#parent: App\User {#1221 ▶}
#related: App\Todo {#1202 ▶}
}
And now we can see the relationsip. Which is
hasMany
relationship.User
is parent and has relation withTodo
So from that I can do the create part.
So finally the store
function of TodoController
will look like below:
public function store(TodoCreateRequest $request)
{
// dd(auth()->user()->todos());
auth()->user()->todos()->create($request->all());
return redirect()->back()->with('message', 'Todo created successfully!');
}
Line Desc: authenticated user has some todos and we are creating this
Here
auth()->user()
will definely the autheticted user. We have no chance to miss the user. Because we have middleware constructor inTodoController
.
Now we don't have to define the
$userId
.
We will make a system so that an authenticated user can see only his
todos
.
in store
function in TodoController.php
if we add below code.
$todos = auth()->user()->todos;
return $todos;
Output in SCREEN will look like below:
[
{
"id": 1,
"title": "new todo",
"user_id": 1,
"completed": 0,
"created_at": "2020-06-20T18:43:13.000000Z",
"updated_at": "2020-06-20T18:43:13.000000Z"
},
{
"id": 4,
"title": "new use",
"user_id": 1,
"completed": 0,
"created_at": "2020-06-21T04:16:32.000000Z",
"updated_at": "2020-06-21T04:16:32.000000Z"
}
]
Below two lines of code for getting the data orderedBy
.
$todos = auth()->user()->todos()->orderBy('completed')->get();
return $todos;
FInally
TodoController.php
Way 1:
public function index()
{
$todos = auth()->user()->todos()->orderBy('completed')->get(); //SQL type of orderBy query
return view('todos.index', compact('todos'));
}
Way 2:
public function index()
{
$todos = auth()->user()->todos->sortBy('completed'); //Collection type
return view('todos.index', compact('todos'));
}
Documentation: Sort By
orderBy
is for SQL, andsortBy
is for collection.
Now we can see
todos
created by specific authenticated user. So todos are protected from other user now.
We can also set
orderBy
inUser
model instead of coding here inTodoController
, then the model and controller will look like below:
User.php
public function todos()
{
return $this->hasMany(Todo::class)->orderBy('completed')->get();
}
TodoController.php
public function index()
{
$todos = auth()->user()->todos();
return $todos;
}
Here we don't need to say
orderBy
inController
. Because we already set this functionality inUser
Model.
Gives Same result
[
{
"id": 1,
"title": "new todo",
"user_id": 1,
"completed": 0,
"created_at": "2020-06-20T18:43:13.000000Z",
"updated_at": "2020-06-20T18:43:13.000000Z"
},
{
"id": 4,
"title": "new use",
"user_id": 1,
"completed": 0,
"created_at": "2020-06-21T04:16:32.000000Z",
"updated_at": "2020-06-21T04:16:32.000000Z"
}
]
But Primary Way is better to use because we can change the order at any time. Because setting this in Controller
is way easier than Model
.
Currently after login that redirect to the
home
page. But I want redirect to thetodo
page.
Dir:
seven\app\Http\Controllers\Auth\LoginController.php
is responsible for log in.
Default Route After log in:
protected $redirectTo = RouteServiceProvider::HOME;
Changing 'HOME'
to '/todo'
for redirecting when login.
LoginController.php
protected $redirectTo = '/todo';
Now we are redirect to the
/todo
page after login.
Similartly we change the redirect page after registering.
RegisterController.php
protected $redirectTo = '/todo';
We can add below condition in
index.blade.php
so that of any user have no todo, then user will se the message that,"No task availabe here."
todos/index.blade.php
@if($todos->count() > 0)
@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach
@else
<p> No task availabe here.</p>
@endif
Documentation Loops
Use
forelse
instead offoreach
.
So instead of way 1
we use way 2
todos/index.blade.php
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse
Way 2
is much smarter thanway 1
Now beautify create.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4 px-4">
<h1 class="text-2xl pb-4">What next you need To-Do</h1>
<a href="{{route('todo.index')}}" class="mx-5 py-2 text-gray-400 cursor-pointer text-white">
<span class="fas fa-arrow-left"></span>
</a>
</div>
<x-alert/>
<form action="{{ route('todo.store') }}" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<input type="text" name="title" class="py-2 px-2 border"/>
<input type="submit" value="Create" class="p-2 border rounded"/>
</form>
@endsection
arrow back button using font awesome is added instead of normal button.
Now beautify edit.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4 px-4">
<h1 class="text-2xl pb-4">Update this Todo list</h1>
<a href="{{route('todo.index')}}" class="mx-5 py-2 text-gray-400 cursor-pointer text-white">
<span class="fas fa-arrow-left"></span>
</a>
</div>
<x-alert/>
<form method="post" action="{{route('todo.update',$todo->id)}}" class="py-5">
@csrf
@method('patch')
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border"/>
<input type="submit" value="Update" class="p-2 border rounded"/>
</form>
@endsection
Redirect to the index
page after creating a todo
is using the following code:
TodoController.php
public function store(TodoCreateRequest $request)
{
auth()->user()->todos()->create($request->all());
return redirect(route('todo.index'))->with('message', 'Todo created successfully!');
}
Add $table->text('description');
in Todo Table
in mirgations.
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
And also fill the mask assignment.
Todo.php
class Todo extends Model
{
protected $fillable = ['title','completed','user_id','description'];
}
Or we can use Guard
so that we dont need to fill the column
Todo.php
class Todo extends Model
{
protected $guarded = [];
}
Now in dev server try to create a
todo
. But error occurs. Because we didn't create a description field in create page.Description
field is required to fill. So to solve this error we must create a field in create page to adddescription
in that column. So that we can fill that column.
The form in create page will look like below:
todos/create.blade.php
<form action="{{ route('todo.store') }}" method="POST" enctype="multipart/form-data" class="py-5">
@csrf <!-- this @csrf token handles routes in form -->
<div class="py-1">
<input type="text" name="title" class="py-2 px-2 border" placeholder="Title"/>
</div>
<div class="py-1">
<textarea name="description" class="p-2 border rounded" placeholder="Description"></textarea>
</div>
<div class="py-1">
<input type="submit" value="Create" class="p-2 border rounded"/>
</div>
</form>
We can also escape the error by using the following code in table
$table->text('description')->nullable();
So that this field have been set to
not-required
. By default it wasrequired
.
Let's first create a link over title
using built-in show method of resources
in the index.blade.php
file
<a class="cursor-pointer" href="{{ route('todo.show', $todo->id) }}">{{$todo->title}}</a>
Now create a show()
function inside TodoController.php
.
public function show(Todo $todo)
{
// return $todo;
return view('todos.show', compact('todo'));
}
To view the page let's create another pages named todos/show.blade.php
@extends('todos.layout')
@section('content')
<div class="flex justify-center border-b pb-4 px-4">
<h1 class="text-2xl pb-4">{{$todo->title}}</h1>
<a href="{{route('todo.index')}}" class="mx-5 py-2 text-gray-400 cursor-pointer text-white">
<span class="fas fa-arrow-left"></span>
</a>
</div>
<div>
<div>
<p>{{ $todo->description }}</p>
</div>
</div>
@endsection
Before..., we can only update the
title
. Now we gonna update the page to update thedescription
column too.
The edit.blade.php
form will look like below:
<form method="post" action="{{route('todo.update',$todo->id)}}" class="py-5">
@csrf
@method('patch')
<div class="py-1">
<input type="text" name="title" value="{{ $todo->title }}" class="py-2 px-2 border" placeholder="Title"/>
</div>
<div class="py-1">
<textarea name="description" class="p-2 border rounded" placeholder="Description">{{ $todo->description }}</textarea>
</div>
<div class="py-1">
<input type="submit" value="Update" class="p-2 border rounded"/>
</div>
</form>
To update the Description we need to edit the update
function inside TodoController.php
public function update(TodoCreateRequest $request, Todo $todo)
{
$todo->update(['description' => $request ->description]);
$todo->update(['title' => $request ->title]);
return redirect(route('todo.index'))->with('message','Updated!');
}